multithreading - Técnicamente, ¿por qué los procesos en Erlang son más eficientes que los hilos del sistema operativo?
green-threads lightweight-processes (7)
Como el intérprete de Erlang solo tiene que preocuparse por sí mismo, el sistema operativo tiene muchas otras cosas de qué preocuparse.
Características de Erlang
De Erlang Programming (2009):
La concurrencia de Erlang es rápida y escalable. Sus procesos son livianos porque la máquina virtual Erlang no crea una secuencia de sistema operativo para cada proceso creado. Se crean, programan y manejan en la VM, independientemente del sistema operativo subyacente. Como resultado, el tiempo de creación del proceso es del orden de microsegundos e independiente del número de procesos existentes simultáneamente. Compare esto con Java y C #, donde para cada proceso se crea un subproceso del sistema operativo subyacente: obtendrá comparaciones muy competitivas, con Erlang superando en gran medida a ambos idiomas.
Desde la programación orientada a Concurrencia en Erlang (pdf) (slides) (2003):
Observamos que el tiempo necesario para crear un proceso de Erlang es constante de 1 μs hasta 2.500 procesos; a partir de entonces aumenta a aproximadamente 3μs para hasta 30,000 procesos. El rendimiento de Java y C # se muestra en la parte superior de la figura. Para una pequeña cantidad de procesos, se necesitan aproximadamente 300 μs para crear un proceso. Crear más de dos mil procesos es imposible.
Vemos que para hasta 30,000 procesos, el tiempo para enviar un mensaje entre dos procesos de Erlang es de aproximadamente 0.8μs. Para C # toma alrededor de 50μs por mensaje, hasta la cantidad máxima de procesos (que fueron aproximadamente 1800 procesos). Java fue aún peor, para un proceso de hasta 100 se necesitaron aproximadamente 50 μs por mensaje, y luego aumentó rápidamente a 10 ms por mensaje cuando había alrededor de 1000 procesos Java.
Mis pensamientos
No entiendo completamente por qué los procesos de Erlang son mucho más eficientes en el desarrollo de nuevos procesos y tienen huellas de memoria mucho más pequeñas por proceso. Tanto el sistema operativo como Erlang VM tienen que programar, cambiar el contexto y hacer un seguimiento de los valores en los registros, etc.
Simplemente ¿por qué no se implementan los subprocesos del sistema operativo de la misma manera que los procesos en Erlang? ¿Tienen que apoyar algo más? ¿Y por qué necesitan una mayor huella de memoria? ¿Y por qué tienen un desove y una comunicación más lentos?
Técnicamente, ¿por qué los procesos en Erlang son más eficientes que los hilos del sistema operativo cuando se trata de desove y comunicación? ¿Y por qué no se pueden implementar y administrar los hilos en el sistema operativo de la misma manera eficiente? ¿Y por qué los hilos del sistema operativo tienen una huella de memoria más grande, además de un desove y comunicación más lentos?
Más lectura
Creo que Jonas quería algunos números para comparar los hilos del sistema operativo con los procesos de Erlang. El autor de Programming Erlang, Joe Armstrong, hace un tiempo probó la escalabilidad del desove de los procesos de Erlang a los hilos del sistema operativo. Escribió un servidor web simple en Erlang y lo probó contra Apache de subprocesos múltiples (ya que Apache usa subprocesos del sistema operativo). Hay un sitio web antiguo con datos que datan de 1998. Solo he logrado encontrar ese sitio una vez. Entonces no puedo suministrar un enlace. Pero la información está por ahí. El punto principal del estudio mostró que Apache alcanzó un máximo de 8K procesos, mientras que su servidor Erlang escrito a mano manejó procesos de 10K +.
Después de investigar un poco más, encontré una presentación de Joe Armstrong.
Desde Erlang - software para un mundo concurrente (presentación) (a los 13 min):
[Erlang] es un lenguaje concurrente; con eso quiero decir que los hilos son parte del lenguaje de programación, no pertenecen al sistema operativo. Eso es realmente lo que está mal con los lenguajes de programación como Java y C ++. Sus hilos no están en el lenguaje de programación, los hilos son algo en el sistema operativo y heredan todos los problemas que tienen en el sistema operativo. Uno de los problemas es la granularidad del sistema de gestión de memoria. La administración de la memoria en el sistema operativo protege páginas enteras de la memoria, por lo que el tamaño más pequeño que un hilo puede ser es el tamaño más pequeño de una página. Eso es realmente muy grande.
Si agrega más memoria a su máquina -tiene la misma cantidad de bits que protege la memoria para que la granularidad de las tablas de páginas aumente- termina utilizando 64kB por ejemplo para un proceso que sabe que se ejecuta en unos cientos de bytes.
Creo que responde si no todas, al menos algunas de mis preguntas
Hay varios factores que contribuyen:
- Los procesos de Erlang no son procesos de sistema operativo. Ellos son implementados por la máquina virtual de Erlang utilizando un modelo de subprocesamiento cooperativo liviano (preventivo en el nivel de Erlang, pero bajo el control de un tiempo de ejecución programado cooperativamente). Esto significa que es mucho más económico cambiar de contexto, ya que solo cambian en puntos conocidos controlados y, por lo tanto, no tienen que guardar el estado completo de la CPU (registros normales, SSE y FPU, mapeo del espacio de direcciones, etc.).
- Los procesos de Erlang utilizan pilas asignadas dinámicamente, que comienzan muy pequeñas y crecen según sea necesario. Esto permite el desarrollo de muchos miles, incluso millones, de procesos de Erlang sin absorber toda la memoria RAM disponible.
- Erlang solía ser de subproceso único, lo que significa que no había ningún requisito para garantizar la seguridad de subprocesos entre procesos. Ahora es compatible con SMP, pero la interacción entre los procesos de Erlang en el mismo planificador / núcleo sigue siendo muy ligera (hay colas de ejecución separadas por núcleo).
Implementé corutinas en el ensamblador y medí el rendimiento.
Alternar entre corutinas, también conocidos como procesos de Erlang, toma alrededor de 16 instrucciones y 20 nanosegundos en un procesador moderno. Además, a menudo se sabe el proceso al que se está cambiando (ejemplo: un proceso que recibe un mensaje en su cola puede implementarse como una transferencia directa del proceso de llamada al proceso de recepción) para que el programador no entre en juego, haciendo es una operación O (1).
Para cambiar los hilos del sistema operativo, tarda entre 500 y 1000 nanosegundos, porque está llamando al kernel. El programador de subprocesos del sistema operativo podría ejecutarse en O (log (n)) o O (log (log (n))) tiempo, que comenzará a notarse si tiene decenas de miles, o incluso millones de subprocesos.
Por lo tanto, los procesos de Erlang son más rápidos y escalan mejor porque la operación fundamental de conmutación es más rápida y el planificador se ejecuta con menos frecuencia.
Los procesos de Erlang corresponden (aproximadamente) a hilos verdes en otros idiomas; no hay separación forzada por OS entre los procesos. (Puede haber una separación impuesta por el lenguaje, pero esa es una protección menor a pesar de que Erlang está haciendo un mejor trabajo que la mayoría.) Debido a que son mucho más livianos, se pueden usar mucho más extensamente.
Los subprocesos del sistema operativo, por otro lado, se pueden programar simplemente en diferentes núcleos de CPU, y (en su mayoría) son capaces de admitir el procesamiento independiente vinculado a la CPU. Los procesos del sistema operativo son como los hilos del sistema operativo, pero con una separación reforzada por el sistema operativo mucho más fuerte. El precio de estas capacidades es que los procesos del sistema operativo y (aún más) los procesos son más costosos.
Otra forma de entender la diferencia es esta. Suponiendo que va a escribir una implementación de Erlang sobre la JVM (no es una sugerencia especialmente loca), entonces haría que cada proceso de Erlang sea un objeto con algún estado. Entonces, tendrías un conjunto de instancias Thread (generalmente de acuerdo con el número de núcleos en tu sistema host, ese es un parámetro ajustable en tiempo real de ejecución de Erlang) que ejecuta los procesos de Erlang. A su vez, eso distribuirá el trabajo que se realizará a través de los recursos del sistema real disponibles. Es una manera bastante ordenada de hacer las cosas, pero se basa totalmente en el hecho de que cada proceso individual de Erlang no hace mucho. Eso está bien, por supuesto; Erlang está estructurado para no requerir que esos procesos individuales sean pesados, ya que es el conjunto general de ellos el que ejecuta el programa.
En muchos sentidos, el verdadero problema es el de la terminología. Las cosas que Erlang llama procesos (y que corresponden fuertemente al mismo concepto en CSP, CCS, y particularmente el cálculo π) simplemente no son lo mismo que las cosas que los lenguajes con un patrimonio C (incluyendo C ++, Java, C # y muchos otros) llaman un proceso o un hilo. Hay algunas similitudes (todas involucran alguna noción de ejecución concurrente) pero definitivamente no hay equivalencia. Así que tenga cuidado cuando alguien le diga "proceso"; ellos pueden entender que signifique algo completamente diferente ...
uno de los motivos es que el proceso de erlang se crea no en el SO, sino en el evm (máquina virtual erlang), por lo que el costo es menor.