javascript - tutorial - node js vs java español
¿Cómo es Node.js inherentemente más rápido cuando aún depende de Threads internamente? (6)
Acabo de ver el siguiente video: Introducción a Node.js y aún no entiendo cómo obtiene los beneficios de velocidad.
Principalmente, en un momento dado, Ryan Dahl (creador de Node.js) dice que Node.js se basa en bucles de eventos en lugar de en subprocesos. Los subprocesos son caros y solo deben dejarse a los expertos de programación concurrente que se utilicen.
Más tarde, luego muestra la pila de arquitectura de Node.js que tiene una implementación C subyacente que tiene su propio grupo de subprocesos internamente. Así que, obviamente, los desarrolladores de Node.js nunca iniciarían sus propios subprocesos o utilizarían el conjunto de subprocesos directamente ... usarían devoluciones de llamadas asíncronas. Eso lo entiendo mucho.
Lo que no entiendo es el punto en el que Node.js sigue usando subprocesos ... simplemente está ocultando la implementación. Entonces, ¿cómo es más rápido si 50 personas solicitan 50 archivos (que actualmente no están en la memoria) y no son necesarios 50 subprocesos? ?
La única diferencia es que, dado que se gestiona internamente, el desarrollador de Node.js no tiene que codificar los detalles de los subprocesos, pero por debajo sigue utilizando los subprocesos para procesar las solicitudes de archivos de IO (bloqueo).
Entonces, ¿no está realmente tomando un solo problema (subprocesamiento) y ocultándolo mientras el problema sigue existiendo: principalmente subprocesos múltiples, cambio de contexto, interbloqueos ... etc.?
Debe haber algún detalle que todavía no entiendo aquí.
Lo que no entiendo es el punto de que Node.js todavía está usando hilos.
Ryan usa hilos para las partes que están bloqueando (la mayoría de node.js usa IO sin bloqueo) porque algunas partes son increíblemente difíciles de escribir sin bloquear. Pero creo que el deseo de Ryan es tener todo sin bloqueo. En la diapositiva 63 (diseño interno) , ve que Ryan usa libev (biblioteca que abstrae la notificación de eventos asíncronos) para el eventloop sin bloqueo. Debido a que el loop-evento node.js necesita menos hilos, lo que reduce el cambio de contexto, el consumo de memoria, etc.
En realidad hay algunas cosas diferentes que se combinan aquí. Pero comienza con el meme de que los hilos son realmente difíciles. Entonces, si son difíciles, es más probable que, al usar subprocesos para 1) roturas debidas a errores y 2) no se utilicen de la manera más eficiente posible. (2) es el que estás preguntando.
Piensa en uno de los ejemplos que da, donde entra una solicitud y ejecutas alguna consulta, y luego haz algo con los resultados de eso. Si lo escribe de una manera procesal estándar, el código podría tener este aspecto:
result = query( "select smurfs from some_mushroom" );
// twiddle fingers
go_do_something_with_result( result );
Si la solicitud de entrada provocó que creara un nuevo subproceso que ejecutó el código anterior, tendrá un subproceso allí sentado, sin hacer nada mientras se está ejecutando la query()
. (Apache, de acuerdo con Ryan, está usando un solo hilo para satisfacer la solicitud original, mientras que nginx lo está superando en los casos de los que habla porque no lo está).
Ahora, si fuera realmente inteligente, expresaría el código anterior de una manera en la que el entorno podría explotar y haría otra cosa mientras ejecuta la consulta:
query( statement: "select smurfs from some_mushroom", callback: go_do_something_with_result() );
Esto es básicamente lo que node.js está haciendo. Básicamente está decorando, de una manera que es conveniente debido al lenguaje y el entorno, de ahí los puntos sobre los cierres, su código de tal manera que el entorno puede ser inteligente sobre lo que se ejecuta y cuándo. De esa manera, node.js no es nuevo en el sentido de que inventó la E / S asíncrona (no es que alguien haya reclamado algo así), pero es nuevo en la forma en que se expresa es un poco diferente.
Nota: cuando digo que el entorno puede ser inteligente acerca de lo que se ejecuta y cuándo, específicamente lo que quiero decir es que el subproceso que utilizó para iniciar algunas operaciones de E / S ahora se puede usar para manejar alguna otra solicitud, o algún cálculo que se pueda hacer en paralelo, o inicie algún otro I / O paralelo. (No estoy seguro de que el nodo sea lo suficientemente sofisticado como para comenzar a trabajar más para la misma solicitud, pero entiendes la idea).
Los subprocesos se usan solo para tratar con funciones que no tienen una facilidad asíncrona, como stat()
.
La función stat()
siempre está bloqueando, por lo que node.js necesita usar un hilo para realizar la llamada real sin bloquear el hilo principal (bucle de eventos). Potencialmente, no se utilizará ningún subproceso del grupo de subprocesos si no necesita llamar a ese tipo de funciones.
Me temo que estoy "haciendo algo incorrecto" aquí, si es así, bórrame y pido disculpas. En particular, no veo cómo creo las pequeñas anotaciones que algunas personas han creado. Sin embargo, tengo muchas preocupaciones / observaciones que hacer en este hilo.
1) El elemento comentado en el pseudocódigo en una de las respuestas populares
result = query( "select smurfs from some_mushroom" );
// twiddle fingers
go_do_something_with_result( result );
es esencialmente falso. Si el subproceso se está calculando, entonces no está haciendo girar los pulgares, está haciendo el trabajo necesario. Si, por otro lado, simplemente está esperando la finalización de IO, entonces no está usando el tiempo de CPU, el punto principal de la infraestructura de control de hilos en el kernel es que la CPU encontrará algo útil que hacer. La única manera de "girar los pulgares" como se sugiere aquí sería crear un bucle de sondeo, y nadie que haya codificado un servidor web real es lo suficientemente inepto para hacerlo.
2) "Los hilos son difíciles", solo tiene sentido en el contexto del intercambio de datos. Si tiene subprocesos esencialmente independientes, como es el caso cuando se manejan solicitudes web independientes, entonces el subproceso es trivialmente simple, simplemente codifica el flujo lineal de cómo manejar un trabajo y se siente bastante sabiendo que manejará múltiples solicitudes, y cada una Será efectivamente independiente. Personalmente, me atrevería a decir que para la mayoría de los programadores, aprender el mecanismo de cierre / devolución de llamada es más complejo que simplemente codificar la versión de subproceso de arriba a abajo. (Pero sí, si tiene que comunicarse entre los subprocesos, la vida se pone muy difícil realmente rápido, pero no estoy convencido de que el mecanismo de cierre / devolución de llamada realmente cambie eso, solo restringe sus opciones, porque este enfoque todavía es posible con subprocesos De todos modos, esa es otra discusión que no es relevante aquí).
3) Hasta ahora, nadie ha presentado ninguna evidencia real de por qué un tipo particular de cambio de contexto llevaría más o menos tiempo que cualquier otro tipo. Mi experiencia en la creación de núcleos multitarea (a pequeña escala para controladores integrados, nada tan sofisticado como un sistema operativo "real") sugiere que este no sería el caso.
4) Todas las ilustraciones que he visto hasta la fecha con el propósito de mostrar qué Nodo es más rápido que otros servidores web son horriblemente defectuosas, sin embargo, tienen defectos de una manera que ilustra indirectamente una ventaja que definitivamente aceptaría para Nodo (y de ninguna manera es insignificante). Nodo no parece que necesite (ni siquiera permite, en realidad) la afinación. Si tiene un modelo con hilos, debe crear suficientes hilos para manejar la carga esperada. Haz esto mal, y terminarás con un bajo rendimiento. Si hay muy pocos subprocesos, entonces la CPU está inactiva, pero no puede aceptar más solicitudes, crea demasiados subprocesos, y desperdiciará la memoria del kernel, y en el caso de un entorno Java, también desperdiciará la memoria del montón principal . Ahora, para Java, perder el montón es la primera, la mejor, forma de arruinar el rendimiento del sistema, porque la recolección de basura eficiente (actualmente, esto podría cambiar con G1, pero parece que el jurado aún está deliberando sobre ese punto a principios de 2013 al menos) depende de tener un montón de montón de repuesto. Por lo tanto, está el problema, afínelo con muy pocos subprocesos, tiene CPU inactivas y rendimiento deficiente, afínelo con demasiados y se atasca de otras maneras.
5) Hay otra manera en la que acepto la lógica de la afirmación de que el enfoque de Node "es más rápido por diseño", y esa es la siguiente. La mayoría de los modelos de subprocesos utilizan un modelo de cambio de contexto de intervalos de tiempo, superpuestos al modelo preventivo más apropiado (alerta de juicio de valor :) y más eficiente (no un juicio de valor). Esto sucede por dos razones: primero, la mayoría de los programadores no parecen entender la prioridad de prioridad, y segundo, si aprendes a crear subprocesos en un entorno de Windows, la división de tiempo está ahí, te guste o no (por supuesto, esto refuerza el primer punto). notablemente, las primeras versiones de Java usaron la prioridad de prioridad en las implementaciones de Solaris y la división de tiempos en Windows. Debido a que la mayoría de los programadores no entendieron y se quejaron de que "los hilos no funcionan en Solaris" cambiaron el modelo a intervalos de tiempo en todas partes). De todos modos, la conclusión es que la división de tiempo crea cambios de contexto adicionales (y potencialmente innecesarios). Cada cambio de contexto requiere tiempo de CPU, y ese tiempo se elimina efectivamente del trabajo que se puede realizar en el trabajo real en cuestión. Sin embargo, la cantidad de tiempo invertido en el cambio de contexto debido a la división de tiempo no debe ser más que un porcentaje muy pequeño del tiempo total, a menos que esté sucediendo algo extravagante, y no hay ninguna razón por la que pueda ver para esperar que ese sea el caso en una servidor web simple). Entonces, sí, los excesos de cambio de contexto involucrados en la división de tiempo son ineficientes (y esto no ocurre en los hilos del núcleo como una regla, por cierto) pero la diferencia será un pequeño porcentaje del rendimiento, no el tipo de factores de número entero que están implícitos en las declaraciones de rendimiento que a menudo están implícitas para el nodo.
De todos modos, pido disculpas por que todo sea largo y desenfrenado, pero realmente siento que hasta ahora, la discusión no ha demostrado nada, y me complacería escuchar a alguien en cualquiera de estas situaciones:
a) una explicación real de por qué Node debería ser mejor (más allá de los dos escenarios que he descrito anteriormente, el primero de los cuales (sintonización deficiente) creo que es la explicación real de todas las pruebas que he visto hasta ahora). ], en realidad, cuanto más lo pienso, más me pregunto si la memoria utilizada por un gran número de pilas puede ser significativa aquí. Los tamaños de pila predeterminados para hilos modernos tienden a ser bastante grandes, pero la memoria asignada por un sistema de eventos basado en el cierre sería solo lo que se necesita)
b) un punto de referencia real que realmente da una oportunidad justa al servidor de subprocesos elegido. Al menos de esa manera, tendría que dejar de creer que las afirmaciones son esencialmente falsas;> ([editar] eso es probablemente más fuerte de lo que pretendía, pero creo que las explicaciones de los beneficios de rendimiento están incompletas en el mejor de los casos, y los puntos de referencia mostrados no son razonables).
Saludos, Toby
No sé nada sobre el funcionamiento interno de node.js, pero puedo ver cómo usar un bucle de eventos puede superar el manejo de E / S en subprocesos. Imagina una solicitud de disco, dame staticFile.x, haz 100 solicitudes para ese archivo. Cada solicitud normalmente toma un hilo que recibe ese archivo, eso es 100 hilos.
Ahora imagine la primera solicitud creando un subproceso que se convierta en un objeto editor, todas las otras 99 solicitudes miran primero si hay un objeto publicador para staticFile.x, si es así, escúchelo mientras está funcionando, de lo contrario, inicie un nuevo subproceso y, por lo tanto, Nuevo objeto editor.
Una vez que se realiza el único hilo, transfiere staticFile.x a los 100 oyentes y se destruye a sí mismo, de modo que la siguiente solicitud crea un nuevo hilo y un nuevo objeto de editor.
Así que son 100 hilos frente a 1 en el ejemplo anterior, pero también 1 búsqueda de disco en lugar de 100 búsquedas de disco, la ganancia puede ser bastante fenomenal. Ryan es un chico inteligente!
Otra forma de ver es uno de sus ejemplos al comienzo de la película. En lugar de:
pseudo code:
result = query(''select * from ...'');
De nuevo, 100 consultas separadas a una base de datos versus ...:
pseudo code:
query(''select * from ...'', function(result){
// do stuff with result
});
Si una consulta ya estaba en curso, otras consultas iguales simplemente saltarían al carro, por lo que puede tener 100 consultas en un solo viaje de ida y vuelta a la base de datos.
¡Nota! Esta es una vieja respuesta. Si bien aún es cierto en términos generales, algunos detalles podrían haber cambiado debido al rápido desarrollo de Node en los últimos años.
Está utilizando hilos porque:
- La opción O_NONBLOCK de abrir () no funciona en archivos .
- Hay bibliotecas de terceros que no ofrecen IO sin bloqueo.
Para falsificar la IO sin bloqueo, los subprocesos son necesarios: realice el bloqueo de IO en una hebra separada. Es una solución fea y causa mucha sobrecarga.
Es aún peor en el nivel de hardware:
- Con DMA la CPU descarga de forma asíncrona IO.
- Los datos se transfieren directamente entre el dispositivo IO y la memoria.
- El kernel envuelve esto en una llamada al sistema de bloqueo síncrono.
- Node.js envuelve la llamada del sistema de bloqueo en un hilo.
Esto es simplemente estúpido e ineficiente. ¡Pero al menos funciona! Podemos disfrutar de Node.js porque oculta los detalles feos y engorrosos detrás de una arquitectura asíncrona controlada por eventos.
Tal vez alguien implementará O_NONBLOCK para archivos en el futuro? ...
Edición: lo comenté con un amigo y él me dijo que una alternativa a los subprocesos es encuestar con select : especifique un tiempo de espera de 0 y realice IO en los descriptores de archivos devueltos (ahora que se garantiza que no se bloquearán).