Acabo de ver el siguiente video: Introducción a Node.js y todavía no entiendo cómo obtienes los beneficios de velocidad.
Principalmente, en un momento, 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 en manos de los expertos en programación concurrente.
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. Entonces, obviamente, los desarrolladores de Node.js nunca iniciarían sus propios subprocesos o usarían el grupo de subprocesos directamente ... usan devoluciones de llamadas asíncronas. Eso lo entiendo.
Lo que no entiendo es el punto de que Node.js todavía está usando subprocesos ... solo está ocultando la implementación, entonces, ¿cómo es esto más rápido si 50 personas solicitan 50 archivos (que actualmente no están en la memoria) y no se requieren 50 subprocesos? ?
La única diferencia es que, dado que se administra internamente, el desarrollador de Node.js no tiene que codificar los detalles de los subprocesos, pero debajo todavía está usando los subprocesos para procesar las solicitudes de archivos IO (bloqueo).
Entonces, ¿no estás simplemente tomando un problema (enhebrado) y escondiéndolo mientras ese problema aún existe: principalmente múltiples hilos, cambio de contexto, bloqueos muertos ... etc.?
Debe haber algún detalle que todavía no entiendo aquí.
fuente
select()
es más rápida que los intercambios de contexto de subprocesos.Respuestas:
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, cuando use hilos para 1) se rompan debido a errores y 2) no los use de la manera más eficiente posible. (2) es el que estás preguntando.
Piense en uno de los ejemplos que da, donde entra una solicitud y ejecuta una consulta, y luego haga algo con los resultados de eso. Si lo escribe de forma procesal estándar, el código podría verse así:
Si la solicitud que ingresó le hizo crear un nuevo hilo que ejecutaba el código anterior, tendrá un hilo allí, sin hacer nada mientras
query()
está ejecutando. (Apache, según Ryan, está utilizando un solo hilo para satisfacer la solicitud original, mientras que nginx lo está superando en los casos de los que habla porque no lo es).Ahora, si fuera realmente inteligente, expresaría el código anterior de una manera en la que el entorno podría funcionar y hacer algo más mientras ejecuta la consulta:
Esto es básicamente lo que está haciendo node.js. Básicamente, está decorando, de una manera que sea conveniente debido al lenguaje y el entorno, de ahí los puntos sobre los cierres, su código de tal manera que el entorno pueda 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 asincrónica (no es que alguien haya afirmado algo así), pero es nuevo porque la forma en que se expresa es un poco diferente.
Nota: cuando digo que el entorno puede ser inteligente sobre lo que se ejecuta y cuándo, específicamente lo que quiero decir es que el hilo que usó para iniciar alguna E / S ahora se puede utilizar para manejar otra solicitud, o algún cálculo que se puede hacer en paralelo, o inicie alguna otra E / S paralela. (No estoy seguro de que el nodo sea lo suficientemente sofisticado como para comenzar a trabajar más para la misma solicitud, pero se entiende la idea).
fuente
¡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á usando hilos porque:
Para falsificar IO sin bloqueo, los hilos son necesarios: bloquear IO en un hilo separado. Es una solución fea y causa mucha sobrecarga.
Es aún peor en el nivel de hardware:
Esto es simplemente estúpido e ineficiente. ¡Pero funciona al menos! Podemos disfrutar de Node.js porque oculta los detalles feos y engorrosos detrás de una arquitectura asincrónica basada en eventos.
¿Quizás alguien implementará O_NONBLOCK para archivos en el futuro? ...
Editar: Discutí esto con un amigo y me dijo que una alternativa a los hilos es sondear con select : especifique un tiempo de espera de 0 y haga IO en los descriptores de archivo devueltos (ahora que se garantiza que no se bloquearán).
fuente
Me temo que estoy "haciendo lo incorrecto" aquí, si es así, bórrame y me disculpo. 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
Es esencialmente falso. Si el hilo está computando, entonces no está girando los pulgares, está haciendo el trabajo necesario. Si, por otro lado, simplemente está esperando la finalización de IO, entonces es que no utiliza tiempo de CPU, el punto central de la infraestructura de control del hilo en el kernel es que la CPU se encuentra algo útil que hacer. La única forma 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 como 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 sienta bien sabiendo que manejará múltiples solicitudes, y cada una Será efectivamente independiente. Personalmente, me aventuraría a 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 hilo de arriba a abajo. (Pero sí, si tiene que comunicarse entre los subprocesos, la vida se pone muy difícil muy 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 aún se puede lograr con subprocesos De todos modos, eso '
3) Hasta ahora, nadie ha presentado ninguna evidencia real de por qué un tipo particular de cambio de contexto requerirí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 que pretenden mostrar qué tan rápido es Node que otros servidores web tienen fallas horribles, sin embargo, tienen fallas de una manera que ilustra indirectamente una ventaja que definitivamente aceptaría para Node (y de ninguna manera es insignificante). Nodo parece que no necesita (ni siquiera permite, en realidad) ajuste. Si tiene un modelo con subprocesos, debe crear suficientes subprocesos 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 principal del montón . Ahora, para Java, desperdiciar el montón es la primera y mejor manera de arruinar el rendimiento del sistema, porque la recolección eficiente de basura (actualmente, esto podría cambiar con G1, pero parece que el jurado todavía está fuera de ese punto a principios de 2013 al menos) depende de tener mucho montón de repuesto. Entonces, está el problema, sintonice con muy pocos subprocesos, tiene CPU inactivas y bajo rendimiento, sintonice con demasiados y se atasca de otras maneras.
5) Hay otra forma 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 es esta. La mayoría de los modelos de subprocesos utilizan un modelo de cambio de contexto dividido en el tiempo, en capas sobre el modelo preventivo más apropiado (alerta de juicio de valor :) y más eficiente (no un juicio de valor). Esto sucede por dos razones, en primer lugar, la mayoría de los programadores no parecen entender la prioridad de prioridad, y en segundo lugar, si aprende a enhebrar en un entorno de Windows, la división de tiempo está allí, le guste o no (por supuesto, esto refuerza el primer punto ; en particular, las primeras versiones de Java utilizaron prioridad de prioridad en implementaciones de Solaris y división de tiempo en Windows. Debido a que la mayoría de los programadores no entendieron y se quejaron de que "el subproceso no funciona en Solaris" cambiaron el modelo a timeslice 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 lleva tiempo de CPU, y ese tiempo se elimina efectivamente del trabajo que se puede hacer 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 bastante extraño, y no hay ninguna razón por la que pueda esperar que ese sea el caso en un servidor web simple). Entonces, sí, los cambios de contexto en exceso involucrados en la división de tiempo son ineficientes (y esto no sucede en y ese tiempo se elimina efectivamente del trabajo que se puede hacer 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 bastante extraño, y no hay ninguna razón por la que pueda esperar que ese sea el caso en un servidor web simple). Entonces, sí, los cambios de contexto en exceso involucrados en la división de tiempo son ineficientes (y esto no sucede en y ese tiempo se elimina efectivamente del trabajo que se puede hacer 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 bastante extraño, y no hay ninguna razón por la que pueda esperar que ese sea el caso en un servidor web simple). Entonces, sí, los cambios de contexto en exceso involucrados en la división de tiempo son ineficientes (y esto no sucede enhilos del kernel como regla, por cierto), pero la diferencia será un pequeño porcentaje del rendimiento, no el tipo de factores de números enteros que están implicados en las declaraciones de rendimiento que a menudo están implicadas para Node.
De todos modos, disculpas por que todo sea largo y divagante, pero realmente siento que hasta ahora, la discusión no ha demostrado nada, y me complacería saber de 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 (ajuste pobre) 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 podría ser significativa aquí. Los tamaños de pila predeterminados para hilos modernos tienden a ser bastante grandes, pero la memoria asignada por un el sistema de eventos basado en el cierre sería solo lo que se necesita)
b) un punto de referencia real que realmente brinda 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 dadas para los beneficios de rendimiento son incompletas en el mejor de los casos, y el los puntos de referencia mostrados no son razonables).
Saludos, Toby
fuente
open()
no se puede hacer sin bloqueo?). De esta forma, amortiza cualquier impacto de rendimiento donde el modelo tradicionalfork()
/pthread_create()
a pedido tendría que crear y destruir subprocesos. Y, como se menciona en la posdata a), esto también amortiza el problema del espacio de la pila. Probablemente pueda atender miles de solicitudes con, digamos, 16 hilos de E / S muy bien.Ryan usa subprocesos 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 bloqueo. Pero creo que Ryan desea tener todo sin bloqueo. En la diapositiva 63 (diseño interno) verá que Ryan usa libev (biblioteca que abstrae la notificación de eventos asíncronos) para el bucle de eventos sin bloqueo . Debido al bucle de eventos, node.js necesita subprocesos menores que reducen el cambio de contexto, el consumo de memoria, etc.
fuente
Los subprocesos se usan solo para tratar funciones que no tienen una instalación asincrónica, como
stat()
.La
stat()
función 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.fuente
No sé nada sobre el funcionamiento interno de node.js, pero puedo ver cómo el uso de un bucle de eventos puede superar el manejo de E / S roscado. Imagina una solicitud de disco, dame staticFile.x, haz 100 solicitudes para ese archivo. Cada solicitud normalmente ocupa un subproceso que recupera ese archivo, es decir, 100 subprocesos.
Ahora imagine la primera solicitud creando un hilo que se convierte en un objeto editor, las otras 99 solicitudes primero buscan si hay un objeto editor para staticFile.x, si es así, escúchelo mientras está haciendo su trabajo, de lo contrario, comience un nuevo hilo y, por lo tanto, un Nuevo objeto editor.
Una vez que se realiza el único subproceso, pasa staticFile.x a los 100 oyentes y se destruye a sí mismo, por lo que la siguiente solicitud crea un nuevo subproceso y un nuevo objeto editor.
Entonces, son 100 hilos versus 1 hilo 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 verlo es uno de sus ejemplos al comienzo de la película. En vez de:
De nuevo, 100 consultas separadas a una base de datos versus ...:
Si una consulta ya se estaba ejecutando, otras consultas iguales simplemente saltarían al carro, por lo que puede tener 100 consultas en un solo recorrido de base de datos.
fuente