Técnicamente, ¿por qué los procesos en Erlang son más eficientes que los hilos del sistema operativo?

170

Las características de Erlang

De Erlang Programming (2009):

La concurrencia de Erlang es rápida y escalable. Sus procesos son livianos, ya que la máquina virtual Erlang no crea un subproceso 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 concurrentes existentes. Compare esto con Java y C #, donde para cada proceso se crea un subproceso subyacente del sistema operativo: obtendrá algunas comparaciones muy competitivas, con Erlang superando en gran medida ambos idiomas.

De la programación orientada a la concurrencia en Erlang (pdf) (diapositivas) (2003):

Observamos que el tiempo necesario para crear un proceso 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 un pequeño número de procesos, se necesitan unos 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 Erlang es de aproximadamente 0.8 µs. Para C #, se necesitan aproximadamente 50 µs por mensaje, hasta el número máximo de procesos (que fue aproximadamente 1800 procesos). Java fue aún peor, para hasta 100 procesos, tardó aproximadamente 50 µs por mensaje a partir de entonces aumentó rápidamente a 10 ms por mensaje cuando hubo aproximadamente 1000 procesos Java.

Mis pensamientos

Técnicamente, no entiendo completamente por qué los procesos de Erlang son mucho más eficientes para generar nuevos procesos y tienen huellas de memoria mucho más pequeñas por proceso. Tanto el sistema operativo como la máquina virtual Erlang tienen que hacer programación, cambios de contexto y realizar un seguimiento de los valores en los registros, etc.

Simplemente, ¿por qué los hilos del sistema operativo no se implementan 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 subprocesos en el sistema operativo de la misma manera eficiente? ¿Y por qué los subprocesos del sistema operativo tienen una mayor huella de memoria, además de una reproducción y comunicación más lenta?

Más lectura

Jonas
fuente
1
Antes de intentar comprender la razón por la cual una hipótesis es verdadera, debe establecer si la hipótesis es verdadera, por ejemplo, respaldada por la evidencia. ¿Tiene referencias de comparaciones comparables que demuestren que un proceso de Erlang en realidad es más eficiente que (por ejemplo) un subproceso Java en una JVM actualizada? ¿O una aplicación C que utiliza el proceso del sistema operativo y el soporte de hilos directamente? (Lo último me parece muy, muy improbable. Lo primero solo algo probable). Quiero decir, con un entorno lo suficientemente limitado (punto de Francisco), podría ser cierto, pero me gustaría ver los números.
TJ Crowder
1
@Donal: Como es el caso con tantas otras declaraciones absolutas. :-)
TJ Crowder
1
@Jonas: Gracias, pero llegué hasta la fecha (1998-11-02) y la versión JVM (1.1.6) y me detuve. La JVM de Sun ha mejorado bastante en los últimos 11.5 años (y presumiblemente el intérprete de Erlang también lo ha hecho), particularmente en el área de subprocesos. (Para ser claros, no estoy diciendo que la hipótesis no sea cierta [y Francisco y Donal han señalado por qué Erland puede hacer algo allí]; estoy diciendo que no debería tomarse al pie de la letra sin ser revisado.)
TJ Crowder
1
@Jonas: "... pero supongo que puedes hacerlo en Erlang ..." Es esa parte de "adivinar", amigo. :-) Estás adivinando que el proceso de cambio de Erlang aumenta más allá de los miles. Estás adivinando que lo hace mejor que los subprocesos Java o OS. Adivinar y desarrollador de software no son una gran combinación. :-) Pero creo que he hecho mi punto.
TJ Crowder
17
@TJ Crowder: Instale erlang y ejecute erl +P 1000100 +hms 100y luego escriba {_, PIDs} = timer:tc(lists,map,[fun(_)->spawn(fun()->receive stop -> ok end end) end, lists:seq(1,1000000)]).y espere unos tres minutos para obtener el resultado. Eso es muy simple. Se necesitan 140us por proceso y 1GB de RAM completa en la computadora portátil mía. Pero es directamente de shell, debería ser mejor a partir del código compilado.
Hynek -Pichi- Vychodil

Respuestas:

113

Hay varios factores contribuyentes:

  1. Los procesos de Erlang no son procesos del sistema operativo. La VM Erlang los implementa utilizando un modelo de subprocesamiento cooperativo liviano (preventivo en el nivel Erlang, pero bajo el control de un tiempo de ejecución cooperativamente programado). Esto significa que es mucho más barato cambiar de contexto, ya que solo cambian en puntos conocidos y controlados y, por lo tanto, no tienen que guardar todo el estado de la CPU (registros normales, SSE y FPU, asignación de espacio de direcciones, etc.).
  2. Los procesos de Erlang usan pilas asignadas dinámicamente, que comienzan muy pequeñas y crecen según sea necesario. Esto permite la generación de muchos miles, incluso millones, de procesos de Erlang sin absorber toda la RAM disponible.
  3. Erlang solía ser de un solo subproceso, lo que significa que no había ningún requisito para garantizar la seguridad del subproceso entre los 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).
Marcelo Cantos
fuente
66
Para su segundo punto: Y si el proceso aún no se ha ejecutado, no hay razón para que se le asigne una pila. Además: se pueden jugar varios trucos jugando con el GC de un proceso de modo que nunca acumule memoria. Pero eso es avanzado y algo peligroso :)
DOY CRAP ANSWERS
3
Para su tercer punto: Erlang impone datos inmutables, por lo que la introducción de SMP no debería afectar la seguridad de los subprocesos.
nilskp
@ nilskp, es cierto, erlang también es un lenguaje de programación funcional. Por lo tanto, no hay datos "variables". Esto conduce a la seguridad del hilo.
liuyang1
66
@nilskp: (RE: comentas sobre el punto 3 ...) Aunque el lenguaje en sí tiene un sistema de tipos inmutable, la implementación subyacente (paso de mensajes, planificador, etc.) es una historia completamente diferente. El soporte SMP correcto y eficiente no solo ocurrió con solo pulsar un interruptor.
Marcelo Cantos
@rvirding: Gracias por el apéndice aclaratorio. Me he tomado la libertad de integrar tus puntos en el cuerpo de mi respuesta.
Marcelo Cantos
73

Después de más investigación encontré una presentación de Joe Armstrong.

De 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 administración de memoria. La gestión de memoria en el sistema operativo protege páginas enteras de memoria, por lo que el tamaño más pequeño que puede tener un hilo es el tamaño más pequeño de una página. Eso es realmente demasiado grande.

Si agrega más memoria a su máquina, tiene la misma cantidad de bits que protege la memoria, por lo que aumenta la granularidad de las tablas de páginas , termina usando, digamos, 64kB para un proceso que sabe que se ejecuta en unos pocos cientos de bytes.

Creo que responde, si no todas, al menos algunas de mis preguntas

Jonas
fuente
2
La protección de la memoria en las pilas está ahí por una razón. ¿Erlang simplemente no protege las pilas de diferentes contextos de ejecución a través de la MMU del procesador? (¿Y solo espero lo mejor?) ¿Qué pasa si un hilo usa más que su pequeño stack? (¿Se verifican todas las asignaciones de pila para ver si se necesita una pila más grande? ¿La pila es móvil?)
Thanatos
2
@Thanatos: Erlang no permite que los programas accedan a la memoria o jueguen con la pila. Todas las asignaciones deben pasar por el tiempo de ejecución administrado, tanto el montón como la pila. En otras palabras: la protección de hardware es inútil porque protege contra cosas que no pueden suceder de todos modos. El lenguaje es seguro para el puntero, para la pila, para la memoria y para los tipos. Un proceso no puede usar más que su "pila pequeña" porque la pila crece según sea necesario. Puedes pensar en ello como lo opuesto a pequeño: infinitamente grande. (Pero perezosamente asignado.)
Jörg W Mittag
44
Debe echar un vistazo al sistema operativo Singularity de Microsoft Research. En Singularity, todo el código, el núcleo, los controladores de dispositivos, las bibliotecas y los programas de usuario se ejecutan en el anillo 0 con todos los privilegios del núcleo. Todo el código, el núcleo, los controladores de dispositivos, las bibliotecas y los programas de usuario se ejecutan en un único espacio de direcciones físicas sin protección de memoria. El equipo descubrió que las garantías que ofrece el lenguaje son mucho más fuertes que las garantías que puede ofrecer la MMU, y al mismo tiempo usar la MMU les cuesta hasta un 30% (!!!) en rendimiento. Entonces, ¿por qué usar la MMU si su idioma ya lo hace de todos modos?
Jörg W Mittag
1
El sistema operativo OS / 400 funciona de la misma manera. Solo hay un único espacio de direcciones planas para todos los programas. Y la mayoría de los lenguajes en uso actual tienen las mismas propiedades de seguridad (ECMAScript, Java, C♯, VB.NET, PHP, Perl, Python, Ruby, Clojure, Scala, Kotlin, Groovy, Ceylon, F♯, OCaml, the "Objetivo" parte de "Objective-C", la parte "++" de "C ++"). Si no fuera por el código C heredado y las características heredadas de C ++ y Objective-C, ni siquiera necesitaríamos memoria virtual.
Jörg W Mittag
47

Implementé corutinas en ensamblador y medí el rendimiento.

El cambio entre corutinas, también conocido como procesos Erlang, requiere aproximadamente 16 instrucciones y 20 nanosegundos en un procesador moderno. Además, a menudo conoce el proceso al que está cambiando (ejemplo: un proceso que recibe un mensaje en su cola puede implementarse como transferencia directa del proceso de llamada al proceso de recepción) para que el programador no entre en juego, lo que hace que Es una operación O (1).

Para cambiar los subprocesos del sistema operativo, se necesitan entre 500 y 1000 nanosegundos, ya que está llamando al núcleo. El programador de subprocesos del sistema operativo puede ejecutarse en tiempo O (log (n)) u O (log (log (n))), lo que comenzará a ser notable 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 tanto la operación fundamental de la conmutación es más rápida como el programador se ejecuta con menos frecuencia.

Surfer Jeff
fuente
33

Los procesos de Erlang corresponden (aproximadamente) a hilos verdes en otros idiomas; no hay separación forzada por el sistema operativo entre los procesos. (Puede haber una separación forzada por el idioma, pero esa es una protección menor a pesar de que Erlang hace un mejor trabajo que la mayoría). Debido a que son mucho más livianos, se pueden usar mucho más ampliamente.

Los subprocesos del sistema operativo, por otro lado, pueden programarse simplemente en diferentes núcleos de CPU y (en su mayoría) pueden admitir procesamiento independiente vinculado a la CPU. Los procesos del sistema operativo son como subprocesos del sistema operativo, pero con una separación mucho más fuerte impuesta por el sistema operativo. El precio de estas capacidades es que los subprocesos del sistema operativo y (aún más) los procesos son más caros.


Otra forma de entender la diferencia es esta. Suponiendo que iba a escribir una implementación de Erlang encima de la JVM (no es una sugerencia particularmente loca), entonces haría que cada proceso de Erlang sea un objeto con algún estado. Luego tendría un grupo de instancias de Thread (generalmente dimensionadas de acuerdo con el número de núcleos en su sistema host; ese es un parámetro ajustable en tiempos de ejecución reales de Erlang, por cierto) que ejecutan los procesos de Erlang. A su vez, eso distribuirá el trabajo que se realizará entre los recursos reales del sistema disponibles. Es una forma bastante ordenada de hacer las cosas, pero se basa por completosobre 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 uno de 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 las mismas cosas que los lenguajes con herencia C (incluyendo C ++, Java, C # y muchos otros) llaman un proceso o un hilo. Hay algunas similitudes (todas implican alguna noción de ejecución concurrente) pero definitivamente no hay equivalencia. Así que tenga cuidado cuando alguien le diga "proceso"; podrían entender que significa algo completamente diferente ...

Compañeros de Donal
fuente
3
Erlang no se acerca al cálculo de Pi. El cálculo de Pi asume eventos síncronos a través de canales que pueden vincularse a variables. Este tipo de concepto no se ajusta en absoluto al modelo Erlang. Intente unirse a Calculus, Erlang está más cerca de eso, aunque todavía necesita poder unirse de forma nativa en algunos mensajes y demás. Hubo un trabajo de tesis (y proyecto) llamado JErlang dedicado que lo implementó.
DOY TERRIBLE ASESORAMIENTO
Todo depende de cómo ve exactamente el cálculo pi (y puede modelar canales asíncronos con canales síncronos más procesos de almacenamiento intermedio).
Donal Fellows
Solo está diciendo que los procesos de Erlang son livianos, pero no está explicando por qué tienen una huella más pequeña (son livianos) y por qué tienen un mejor rendimiento que los subprocesos del sistema operativo.
Jonas
1
@Jonas: Para algunos tipos de tareas (en particular, tareas con gran cantidad de cálculos), los hilos del sistema operativo funcionan mejor. Eso sí, esas no son tareas típicamente para las que se usa Erlang; Erlang se enfoca en tener un gran número de tareas simples de comunicación. Una de las ventajas de hacerlo es que, en el caso de un grupo de tareas que realizan un trabajo y esperan el resultado, todo eso se puede hacer en un solo subproceso del sistema operativo en un solo procesador, que es más eficiente que teniendo cambios de contexto.
Donal Fellows
Teóricamente, también podría hacer que un subproceso del sistema operativo sea muy barato mediante el uso de una pila muy pequeña y controlando cuidadosamente la cantidad de otros recursos específicos del subproceso asignados, pero eso es bastante problemático en la práctica. (Predecir los requisitos de la pila es un poco un arte negro). Por lo tanto, los subprocesos del sistema operativo están especialmente diseñados para ser óptimos en el caso de que haya menos de ellos (del orden del número de núcleos de CPU) y donde están haciendo más significativos cantidades de procesamiento de cada uno.
Donal Fellows
3

Creo que Jonas quería algunos números al comparar los hilos del sistema operativo con los procesos de Erlang. El autor de Programming Erlang, Joe Armstrong, hace un tiempo probó la escalabilidad de la generación de procesos de Erlang en subprocesos del sistema operativo. Escribió un servidor web simple en Erlang y lo probó contra Apache multiproceso (ya que Apache usa hilos del sistema operativo). Hay un sitio web antiguo con datos que datan de 1998. Solo he logrado encontrar ese sitio exactamente una vez. Entonces no puedo proporcionar un enlace. Pero la información está ahí afuera. El punto principal del estudio mostró que Apache alcanzó un máximo de 8K procesos, mientras que su servidor Erlang escrito a mano manejó más de 10K procesos.

Jurnell
fuente
55
Creo que está hablando de este: sics.se/~joe/apachevsyaws.html Pero le pregunté cómo erlang hace que los hilos sean tan eficientes en comparación con los hilos kerlenl.
Jonas
@Jonas link está muerto. La última instantánea está aquí
alvaro g
1
El artículo decía: "Apache muere en aproximadamente 4,000 sesiones paralelas. Yaws todavía funciona en más de 80,000 conexiones paralelas".
Nathan Long
vea el artículo completo en citeseerx.ist.psu.edu/viewdoc/… De hecho, resultó imposible romper el servidor Erlang usando 16 máquinas atacantes, aunque fue fácil detener el servidor Apache.
Bernhard
1

Debido a que el intérprete de Erlang solo tiene que preocuparse por sí mismo, el sistema operativo tiene muchas otras cosas de las que preocuparse.

Francisco Soto
fuente
0

Una de las razones es que el proceso erlang no se crea en el sistema operativo, sino en evm (máquina virtual erlang), por lo que el costo es menor.

ruidosamente
fuente