¿Los diferentes idiomas introducen su propia sobrecarga cuando hacen computación paralela?

Si está pensando estrictamente en código multiproceso , entonces debe tener en cuenta algunas restricciones con las que se encontrará al utilizar diferentes tiempos de ejecución de lenguaje.

Tocaré algunos ejemplos a continuación, pero antes de hacerlo, considere lo siguiente:

  • Paralelismo no es sinónimo de enhebrado. Subprocesar es solo una forma de lograr código que se ejecuta en paralelo.
  • Muchos de los desafíos de rendimiento del código enhebrado no provienen de la existencia de hilos, sino de problemas para acceder a los datos compartidos entre los hilos. Busque bibliotecas en cada idioma diseñadas con paralelismo en mente.
  • En general, al elegir entre idiomas, use el idioma con el que esté más familiarizado y comprenda su método preferido para lograr el paralelismo. Por ejemplo, en python, el multiprocesamiento y / o las rutinas se consideran formas apropiadas para lograr el paralelismo. Multithreading generalmente no lo es.

De acuerdo con esas cosas en mente, permítanme dar algunos ejemplos de problemas específicos de tiempo de ejecución con subprocesos:

  • El Oracle Hotspot JVM es conocido por tener un buen soporte de subprocesos del sistema operativo y el JDK tiene buenas primitivas de concurrencia, así como un bloqueo de grano fino / bloqueo / seguridad de subprocesos en toda la biblioteca estándar. En general, escribir código enhebrado en la JVM es una práctica común y muy bien soportada, ya sea a través de Java u otro lenguaje JVM.
  • Vale la pena señalar que la JVM tiene un recolector de basura que puede funcionar en paralelo a otros subprocesos (sin detenerlos). Este es un beneficio sobre otros tiempos de ejecución de recolección de basura.
  • Se sabe que CPython tiene un bloqueo de intérprete global (http://wiki.python.org/moin/Glob…), que evita que el código Python puro se ejecute en múltiples núcleos al mismo tiempo. Dicho esto, si llama a una biblioteca nativa, ese código puede ejecutarse en paralelo a otro código de Python. Tenga en cuenta que otros tiempos de ejecución de Python, como Jython (JVM) e IronPython (.Net CLR), no tienen esta construcción y admiten subprocesos múltiples.
  • De manera similar con Ruby: MRI tiene un GIL, pero MacRuby (tiempo de ejecución Obj-C) y JRuby (JVM) no.
  • Los sistemas operativos POSIX exponen pthreads a los programadores C / C ++, que son hilos directamente del sistema operativo, pero tendrá que construir o traer (es decir, aumentar) su propia sincronización en torno a las estructuras de datos. Esto le brinda un control preciso sobre el rendimiento de su aplicación, pero deja mucho margen de error.
  • Go es conocido por proporcionar una interfaz de subprocesamiento sólida con primitivas (canales) de mensajes de primera clase para la sincronización. Las propiedades de programación funcional de Go hacen que lograr un paralelismo seguro sea un poco más fácil.

Hay muchos otros lenguajes que tienen soporte completo para subprocesos (puede ir directamente a la implementación de subprocesos con la C correcta), pero no hay un tiempo de ejecución único que esté muy por encima de los demás. La mayoría de estos tiempos de ejecución utilizan subprocesos según lo dispuesto por el sistema operativo debajo de ellos.

Finalmente, preguntaste específicamente sobre ‘gastos generales’, pero no hay una única forma de definir los gastos generales:

Cada uno de estos lenguajes tiene una “sobrecarga” de ejecución diferente, ya sea memoria o huella de la CPU, pero esa variación existe independientemente del subproceso. En su mayor parte, debido a que la mayoría de los idiomas implementan hilos sobre pthreads, las huellas son las mismas.

Al diseñar sistemas paralelos, los dos problemas más importantes que afectan la escalabilidad fuera de los algoritmos son los modelos de bloqueo / coordinación y los modelos de administración de memoria. Algunos idiomas tienen un fuerte sesgo hacia un comportamiento u otro, aunque pocos lo fuerzan estrictamente. Puede hackear un comportamiento paralelo decente en la mayoría de los idiomas, simplemente no es un código de aspecto natural en muchos idiomas.

La mayoría de los sistemas paralelos masivos no tienen bloqueos ni recursos compartidos significativos; Los modelos preferidos son corutinas, fibras e instalaciones similares combinadas con comportamientos sin bloqueo que permiten la programación cooperativa. Si bien pueden enhebrarse por conveniencia, también podrían ser procesos separados y funcionar igual de bien. Esta es una elección arquitectónica intencional, ya que el bloqueo y el cambio de contexto son una fuente importante de pérdida de rendimiento y falta de escalabilidad. En consecuencia, la mayoría de los algoritmos paralelos se implementan utilizando arquitecturas de paso de mensajes. Esto puede sonar como un sistema paralelo débilmente acoplado, pero un protocolo de mensajería distribuida bien diseñado puede producir un sistema estrechamente coordinado sin bloqueos. Esto tiene beneficios incluso cuando todos los procesos / hilos están en la misma máquina. En principio, puede hacer esto en la mayoría de los idiomas, pero es más natural en algunos que en otros. La mayoría de los llamados algoritmos “sin bloqueo” aún requieren sincronización, es menos costoso.

Debido a que los algoritmos paralelos tienden a ser sensibles a la latencia, a menudo son sensibles a los modelos de administración de memoria. Los recolectores de basura (por ejemplo, Java) son asesinos de rendimiento en este contexto. Además, los lenguajes que hacen la mayoría de las cosas a través de objetos relativamente hinchados y una densidad de memoria relativamente baja como Java tienen una eficiencia de caché de CPU más pobre en relación con lenguajes como C / C ++, que también agrega latencia. Por último, los lenguajes de tipo estático que minimizan la cantidad de despacho dinámico tienden a ser más eficientes debido a la menor probabilidad de fallas en la línea de caché. Ninguno de estos son showstoppers; puede construir un sistema paralelo masivo en Java o Python, pero el rendimiento estará lejos de ser óptimo para muchos tipos de algoritmos paralelos en relación con lo que podría hacer en un lenguaje como C, C ++ o FORTRAN.

Al final del día, mucho tiene que ver con los algoritmos que planea ejecutar. Si se acoplan muy libremente con requisitos de latencia mínimos, entonces un sistema como Hadoop puede funcionar bien sin importar en qué idioma se implemente. Para sistemas paralelos más acoplados o sistemas donde el rendimiento y la latencia son críticos, casi todo el desarrollo se realiza en C y C ++ hoy porque esos lenguajes aún ofrecen ventajas de factores enteros para muchos códigos.

Agregaré que los idiomas son solo herramientas. Si bien es posible escribir sistemas paralelos sorprendentes en C ++, aún debe comprender cómo usar el lenguaje para lograr esos resultados. Proporciona la posibilidad pero no la garantía. Si no planea convertirse en un experto en la implementación de sistemas paralelos, la elección de los idiomas es mucho menos importante porque todos proporcionan las herramientas para el paralelismo básico.