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.