¿Por qué está utilizando 'dejar' dentro de un` para ' loop tan lento en Chrome?


ACTUALIZACIÓN IMPORTANTE.

Pensó que todavía no en la versión principal de Chrome el nuevo Encendido+motores turbofán para Chrome Canary 59 ha resuelto el problema. La prueba muestra tiempos idénticos para las variables de bucle declaradas let y var.


Pregunta original (ahora muda).

Cuando se usa let en un bucle for en Chrome, se ejecuta muy lentamente, en comparación con mover la variable justo fuera del alcance del bucle.

for(let i = 0; i < 1e6; i ++); 

Toma el doble de tiempo as

{ let i; for(i = 0; i < 1e6; i ++);}

¿Qué está pasando?

Fragmento demuestra la diferencia y solo afecta a Chrome y ha sido así durante el tiempo que puedo recordar Chrome apoyo let.

var times = [0,0]; // hold total times
var count = 0;  // number of tests

function test(){
    var start = performance.now();
    for(let i = 0; i < 1e6; i += 1){};
    times[0] += performance.now()-start;
    setTimeout(test1,10)
}
function test1(){
    // this function is twice as quick as test on chrome
    var start = performance.now();
    {let i ; for(i = 0; i < 1e6; i += 1);}
    times[1] += performance.now()-start;
    setTimeout(test2,10)
}

// display results
function test2(){
    var tot =times[0]+times[1];
    time.textContent = tot.toFixed(3)  + "ms";
    time1.textContent = ((times[0]/tot)*100).toFixed(2) + "% " + times[0].toFixed(3)  + "ms";
    time2.textContent = ((times[1]/tot)*100).toFixed(2) + "% " + times[1].toFixed(3) + "ms";
    if(count++ < 1000){;
        setTimeout(test,10);
    }
}
var div = document.createElement("div");
var div1 = document.createElement("div");
var div2 = document.createElement("div");
var time = document.createElement("span");
var time1 = document.createElement("span");
var time2 = document.createElement("span");
div.textContent = "Total execution time : "
div1.textContent = "Test 1 : "
div2.textContent = "Test 2 : "
div.appendChild(time);
div1.appendChild(time1);
div2.appendChild(time2);
document.body.appendChild(div);
document.body.appendChild(div1);
document.body.appendChild(div2);
test2()

Cuando me encontré con esto por primera vez pensé que era debido a la instancia recién creada de i, pero lo siguiente muestra que esto no es así.

Vea el fragmento de código ya que he eliminado cualquier posibilidad de que la declaración let adicional se optimice con ini con aleatorio y luego se suma al valor indeterminado de k.

También agregué un segundo contador de bucle p

var times = [0,0]; // hold total times
var count = 0;  // number of tests
var soak = 0; // to stop optimizations
function test(){
    var j;
    var k = time[1];
    var start = performance.now();
    for(let p =0, i = 0; i+p < 1e3; p++,i ++){j=Math.random(); j += i; k += j;};
    times[0] += performance.now()-start;
    soak += k;
    setTimeout(test1,10)
}
function test1(){
    // this function is twice as quick as test on chrome
    var k = time[1];
    var start = performance.now();
    {let p,i ; for(p = 0,i = 0; i+p < 1e3; p++, i ++){let j = Math.random(); j += i; k += j}}
    times[1] += performance.now()-start;
    soak += k;
    setTimeout(test2,10)
}

// display results
function test2(){
    var tot =times[0]+times[1];
    time.textContent = tot.toFixed(3)  + "ms";
    time1.textContent = ((times[0]/tot)*100).toFixed(2) + "% " + times[0].toFixed(3)  + "ms";
    time2.textContent = ((times[1]/tot)*100).toFixed(2) + "% " + times[1].toFixed(3) + "ms";
    if(count++ < 1000){;
        setTimeout(test,10);
    }
}
var div = document.createElement("div");
var div1 = document.createElement("div");
var div2 = document.createElement("div");
var time = document.createElement("span");
var time1 = document.createElement("span");
var time2 = document.createElement("span");
div.textContent = "Total execution time : "
div1.textContent = "Test 1 : "
div2.textContent = "Test 2 : "
div.appendChild(time);
div1.appendChild(time1);
div2.appendChild(time2);
document.body.appendChild(div);
document.body.appendChild(div1);
document.body.appendChild(div2);
test2()
Author: Blindman67, 2016-11-06

3 answers

Actualización: Junio 2018: Chrome ahora optimiza esto mucho mejor de lo que lo hizo cuando esta pregunta y respuesta se publicaron por primera vez; ya no hay ninguna penalización apreciable por usar let en el for si no está creando funciones en el bucle (y si lo está, los beneficios valen la pena el costo).


Porque se crea un nuevo i para cada iteración del bucle, de modo que los cierres creados dentro del bucle se cierran sobre el i para esa iteración . Esto está cubierto por la especificación en el algoritmo para la evaluación de un cuerpo de bucle for , que describe la creación de un nuevo entorno variable por iteración de bucle.

Ejemplo:

for (let i = 0; i < 5; ++i) {
  setTimeout(function() {
    console.log("i = " + i);
  }, i * 50);
}

// vs.
setTimeout(function() {
  let j;
  for (j = 0; j < 5; ++j) {
    setTimeout(function() {
      console.log("j = " + j);
    }, j * 50);
  }
}, 400);

Eso es más trabajo. Si no necesita un nuevo i para cada bucle, use let fuera del bucle. Consulte la actualización anterior, no hay necesidad de evitarlo que no sean casos extremos.

Podemos esperar que ahora que todo menos módulos ha sido implementado, V8 lo hará probablemente mejore la optimización de las cosas nuevas, pero no es sorprendente que la funcionalidad deba priorizarse inicialmente sobre la optimización.

Es genial que otros motores ya hayan hecho la optimización, pero el equipo V8 aparentemente no ha llegado todavía. Ver actualización anterior.

 23
Author: T.J. Crowder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-18 08:11:35

ACTUALIZACIÓN IMPORTANTE.

Pensado todavía no en la versión principal de Chrome el nuevo Encendido+motores turbofán para Chrome Canary 60.0.3087 ha resuelto el problema. La prueba muestra tiempos idénticos para las variables de bucle declaradas let y var.

Nota al margen. Mi código de prueba usa Function.toString() y falló en Canary porque devuelve "function() {" no "function () {" como versiones anteriores (solución fácil usando regexp) sino un problema potencial para aquellos que usan Function.toSting()

Actualización Gracias al usuario Dan. M que proporcionan el enlace https://bugs.chromium.org/p/v8/issues/detail?id=4762 (y heads up) que tiene más sobre el tema.


Respuesta anterior

Optimiser optó por no participar.

Esta pregunta me ha desconcertado durante algún tiempo y las dos respuestas son las respuestas obvias, pero no tenía sentido ya que la diferencia de tiempo era demasiado grande para ser la creación de una nueva variable de ámbito y contexto de ejecución.

En un esfuerzo para probar esto encontré la respuesta.

Respuesta Corta

El optimizador no admite un bucle for con una instrucción let en la declaración.

Perfil de Chrome de las dos funciones mostradas la función lenta no está optimizada Chrome Versión 55.0.2883.35 beta, Windows 10.

Una imagen que vale más que mil palabras, y debería haber sido el primer lugar para mirar.

Las funciones relevantes para el perfil anterior

var time = [0,0]; // hold total times

function letInside(){
    var start = performance.now();

    for(let i = 0; i < 1e5; i += 1); // <- if you try this at home don't forget the ;

    time[0] += performance.now()-start;
    setTimeout(letOutside,10);
}

function letOutside(){ // this function is twice as quick as test on chrome
    var start = performance.now();

    {let i; for(i = 0; i < 1e5; i += 1)}

    time[1] += performance.now()-start;
    setTimeout(displayResults,10);
}

Como Chrome es el jugador principal y el bloqueado las variables de ámbito para contadores de bucle están en todas partes, aquellos que necesitan código de rendimiento y sienten que las variables de ámbito de bloque son importantes function{}(for(let i; i<2;i++}{...})//?WHY?deben considerar por el momento la sintaxis alternativa y declarar el contador de bucle fuera del bucle.

Me gustaría decir que la diferencia de tiempo es trivial, pero a la luz del hecho de que todo el código dentro de la función no está optimizado usando for(let i... debe usarse con cuidado.


 5
Author: Blindman67,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:43

@T. J. Crowder ya respondió a la pregunta del título, pero voy a responder a sus dudas.

Cuando me encontré con esto por primera vez pensé que era debido a la instancia recién creada de i, pero lo siguiente muestra que esto no es así.

En realidad, es debido al ámbito recién creado para la variable i. Que no está (todavía) optimizado ya que es más complicado que un simple alcance de bloque.

Ver el segundo fragmento de código como he eliminado cualquier posibilidad de que la declaración let adicional se optimice con ini con random y luego se agregue al valor indeterminado de k.

Su declaración adicional let j en

{let i; for (i = 0; i < 1e3; i ++) {let j = Math.random(); j += i; k += j;}}
// I'll ignore the `p` variable you had in your code

Fue optimizado hacia fuera . Eso es algo bastante trivial que hacer para un optimizador, puede evitar esa variable por completo simplificando su cuerpo de bucle a

k += Math.random() + i;

El ámbito no es realmente necesario a menos que cree cierres allí o use eval o similar abominación.

Si introducimos tal cierre (como código muerto, esperemos que el optimizador no se dé cuenta de eso) y pit

{let i; for (i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}}

Contra

for (let i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}

Entonces veremos que corren a la misma velocidad.

var times = [0,0]; // hold total times
var count = 0;  // number of tests
var soak = 0; // to stop optimizations
function test1(){
    var k = time[1];
    var start = performance.now();
    {let i; for(i=0; i < 1e3; i++){ let j=Math.random(); k += j+i; function f() { j; }}}
    times[0] += performance.now()-start;
    soak += k;
    setTimeout(test2,10)
}
function test2(){
    var k = time[1];
    var start = performance.now();
    for(let i=0; i < 1e3; i++){ let j=Math.random(); k += j+i; function f() { j; }}
    times[1] += performance.now()-start;
    soak += k;
    setTimeout(display,10)
}

// display results
function display(){
    var tot =times[0]+times[1];
    time.textContent = tot.toFixed(3)  + "ms";
    time1.textContent = ((times[0]/tot)*100).toFixed(2) + "% " + times[0].toFixed(3)  + "ms";
    time2.textContent = ((times[1]/tot)*100).toFixed(2) + "% " + times[1].toFixed(3) + "ms";
    if(count++ < 1000){
        setTimeout(test1,10);
    }
}
var div = document.createElement("div");
var div1 = document.createElement("div");
var div2 = document.createElement("div");
var time = document.createElement("span");
var time1 = document.createElement("span");
var time2 = document.createElement("span");
div.textContent = "Total execution time : "
div1.textContent = "Test 1 : "
div2.textContent = "Test 2 : "
div.appendChild(time);
div1.appendChild(time1);
div2.appendChild(time2);
document.body.appendChild(div);
document.body.appendChild(div1);
document.body.appendChild(div2);
display();
 2
Author: Bergi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:33:16