while length for bucle array javascript node.js ecmascript-6 v8

length - javascript for structure



¿Por qué let es más lento que var en un bucle for en nodejs? (2)

Basado en la diferencia entre la mecánica de var vs. let , está relacionado con el hecho de que var existe en todo el alcance del bloque de la función anónima, mientras que let solo existe dentro del ciclo y debe volverse a declarar para cada iteración. 1 Aquí hay un ejemplo que demuestra este punto:

(function() { for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(`i: ${i} seconds`); }, i * 1000); } // 5, 5, 5, 5, 5 for (let j = 0; j < 5; j++) { setTimeout(function() { console.log(`j: ${j} seconds`); }, 5000 + j * 1000); } // 0, 1, 2, 3, 4 }());

Observe que i se comparte en todas las iteraciones del bucle mientras que let no. Según su punto de referencia, parece que node.js simplemente no ha optimizado las reglas de alcance para let ya que es mucho más reciente y complicado que var .

Elaboración

Aquí hay una pequeña explicación laica de let bucles, para aquellos a quienes no les importa mirar las especificaciones realmente densas, pero tienen curiosidad por cómo se vuelve a declarar let para cada iteración mientras se mantiene la continuidad.

¡Pero let podemos volver a declararlo para cada iteración, porque si lo cambia dentro del bucle, se propaga a la siguiente iteración!

Primero, aquí hay un ejemplo que casi parece validar este posible contraargumento:

(function() { for (let j = 0; j < 5; j++) { j++; // see how it skips 0, 2, and 4!?!? setTimeout(function() { console.log(`j: ${j} seconds`); }, j * 1000); } }());

Tiene razón en parte, ya que los cambios respetan la continuidad de j . Sin embargo, todavía se vuelve a declarar para cada iteración, como lo demuestra Babel:

"use strict"; (function () { var _loop = function _loop(_j) { _j++; // here''s the change inside the new scope setTimeout(function () { console.log("j: " + _j + " seconds"); }, _j * 1000); j = _j; // here''s the change being propagated back to maintain continuity }; for (var j = 0; j < 5; j++) { _loop(j); } })();

Como se dijo. Reglas complicadas No es de extrañar que un punto de referencia muestre una discrepancia tan grande en el rendimiento (por ahora). Esperemos que se optimice aún más en el futuro.

1: Vea esta versión transpilada en REPL de Babel para ver esto demostrado. Lo que sucede cuando declara una variable let en un bucle for como ese es que se crea un nuevo entorno declarativo para contener esa variable ( detalles aquí ), y luego para cada iteración del bucle se crea otro entorno declarativo para contener una copia por iteración de La variable; la copia de cada iteración se inicializa a partir del valor anterior ( detalles aquí ), pero son variables separadas, como se demuestra en el enlace por los valores de salida de los cierres.

He escrito un punto de referencia muy simple:

console.time(''var''); for (var i = 0; i < 100000000; i++) {} console.timeEnd(''var'') console.time(''let''); for (let i = 0; i < 100000000; i++) {} console.timeEnd(''let'')

Si está ejecutando Chrome, puede probarlo aquí (ya que NodeJS y Chrome usan el mismo motor JavaScript, aunque generalmente versiones ligeramente diferentes):

// Since Node runs code in a function wrapper with a different // `this` than global code, do that: (function() { console.time(''var''); for (var i = 0; i < 100000000; i++) {} console.timeEnd(''var'') console.time(''let''); for (let i = 0; i < 100000000; i++) {} console.timeEnd(''let'') }).call({});

Y los resultados me sorprenden:

var: 89.162ms let: 320.473ms

Lo probé en Node 4.0.0 && 5.0.0 && 6.0.0 y la proporción entre var y let es la misma para cada versión de nodo.

¿Podría alguien explicarme cuál es la razón de este comportamiento aparentemente extraño?


Por esta pregunta. Intento encontrar alguna pista del código fuente de Chrome V8. Aquí está el código de pelado del bucle V8:

https://github.com/v8/v8/blob/5.4.156/src/compiler/loop-peeling.cc

Intento entenderlo, considero que el bucle tiene una capa intermedia en la implementación. for loop mantendrá el valor de incremento en la capa media.

Si el bucle usa let para declarar "i", V8 declarará una nueva variable i para cada iteración del bucle, copie el valor de la variable de incremento de la capa intermedia a esa nueva "i" declarada, luego colóquelo en el alcance del cuerpo del bucle;

Si el bucle usa var para declarar "i", V8 solo pondrá la referencia del valor de incremento de la capa intermedia en el alcance del cuerpo del bucle. Disminuirá la sobrecarga de rendimiento de la iteración de bucle.

Perdón por mi piscina inglés. Hay un gráfico en el código fuente v8, que le mostrará el mecanismo.