Pourquoi L'utilisation de " let "dans une boucle" for " Est-elle si lente sur Chrome?

MISE À JOUR MAJEURE.

pensé pas encore sur la version Chrome major le nouveau allumage + turboréacteurs pour Chrome Canary 59 a résolu le problème. Les temps de test sont identiques pour les variables de boucle déclarées let et var .


question originale (maintenant muette).

lorsqu'on utilise let dans une boucle for sur Chrome il tourne très lentement, par rapport au déplacement de la variable en dehors de la boucle.

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

prend deux fois plus de temps que

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

que se passe-t-il?

Snippet montre la différence et n'affecte que le Chrome et a été ainsi aussi longtemps que je me souviens Chrome supportant 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()

Quand j'ai rencontré cette je pensais que c'était à cause de la instance nouvellement créée de je mais la suite montre que ce n'est pas ainsi.

voir code snippet car j'ai éliminé toute possibilité que la déclaration de let supplémentaire soit optimisée avec ini au hasard et ajoute ensuite à la valeur indéterminée de K.

j'ai aussi ajouté un deuxième compteur de boucle 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()
23
demandé sur Blindman67 2016-11-06 15:46:05

3 réponses

mise à jour: juin 2018: Chrome optimise maintenant beaucoup mieux qu'il ne l'a fait lorsque cette question et Réponse ont été affichés pour la première fois; il n'y a plus de pénalité appréciable pour l'utilisation let dans le for si vous ne créez pas des fonctions dans la boucle (et si vous êtes, les avantages en valent la peine).


parce qu'un nouveau i est créé pour chaque itération de la boucle, de sorte que les fermetures ont créé à l'intérieur de la boucle, fermez le i pour cette itération . Ceci est couvert par la spécification dans l'algorithme pour évaluation d'un for corps de boucle , qui décrit la création d'un nouvel environnement variable par itération de boucle.

exemple:

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);

C'est plus de travail. Si vous n'avez pas besoin d'un nouveau i pour chaque boucle, utilisez let en dehors de la boucle. voir Mise à jour ci-dessus, pas besoin de l'éviter autre que les cas de bord.

nous pouvons nous attendre à ce que maintenant que tout sauf les modules a été mis en œuvre, V8 va probablement améliorer l'optimisation de la nouvelle substance, mais il n'est pas surprenant que la fonctionnalité devrait d'abord être prioritaire sur l'optimisation.

il est grand que d'autres moteurs ont déjà fait le optimisation, mais l'équipe V8 n'est pas encore arrivée. Voir mise à jour ci-dessus.

23
répondu T.J. Crowder 2018-06-18 08:11:35

MISE À JOUR MAJEURE.

pensé pas encore sur la version Chrome major le nouveau allumage + turboréacteurs pour Chrome Canary 60.0.3087 a résolu le problème. Le Test montre des temps identiques pour les variables de boucle déclarées let et var .

note de Côté. mon code de test utilise Function.toString() et a échoué sur Canary parce qu'il retourne "function() {" pas "function () {" comme versions précédentes (easy fix using regexp) mais un problème potentiel pour ceux qui utilisent Function.toSting()

Update merci à l'utilisateur Dan. M qui fournissent le lien https://bugs.chromium.org/p/v8/issues/detail?id=4762 (et heads up) qui a plus sur la question.


réponse précédente

Optimiseur choisi.

cette question m'a intrigué pendant un certain temps et les deux réponses sont les réponses évidentes, mais cela n'avait aucun sens puisque la différence de temps était trop grande pour être la création d'une nouvelle variable scoped et le contexte d'exécution.

dans un effort pour prouver ceci j'ai trouvé la réponse.

courte réponse

a pour boucle avec une mention let dans la déclaration n'est pas supporté par le optimiseur.

Chrome profile of the two functions shown the slow function is not optimized Chrome Version 55.0.2883.35 beta, Windows 10.

une image qui vaut mille mots, et qui aurait dû être le premier endroit où regarder.

Les fonctions pertinentes pour le profil ci-dessus

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);
}

comme Chrome est le joueur principal et les variables scoped bloqué pour les compteurs de boucle sont partout, ceux qui ont besoin de code performant et estiment que les variables à portée de bloc sont importantes function{}(for(let i; i<2;i++}{...})//?WHY? devraient considérer pour le moment la syntaxe alternative et déclarer le compteur de boucle en dehors de la boucle.

je voudrais dire que le décalage horaire est trivial, mais à la lumière du fait que tout le code dans la fonction n'est pas optimisé en utilisant for(let i... doit être utilisé avec soin.


5
répondu Blindman67 2017-05-23 12:26:43

@T. J. Crowder a déjà répondu à la question de titre, mais je vais répondre à vos doutes.

Quand j'ai rencontré cette je pensais que c'était à cause de l'instance nouvellement créée de je mais la suite montre que ce n'est pas ainsi.

en fait, c'est en raison de la portée nouvellement créée pour la variable i . Qui n'est pas (encore) optimisé car il est plus compliqué qu'un simple champ d'application de bloc .

voir le deuxième extrait de code car j'ai éliminé toute possibilité que la déclaration de let supplémentaire soit optimisée avec ini au hasard et ajoute ensuite à la valeur indéterminée de K.

votre déclaration supplémentaire let j dans

{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

a été optimisé sur . C'est une chose assez insignifiante à faire pour un optimiseur, il peut éviter que variable tout à fait en simplifiant votre corps de boucle à

k += Math.random() + i;

la portée n'est pas vraiment nécessaire à moins de créer des fermetures ou d'utiliser eval ou des abominations similaires.

si nous introduisons une telle fermeture (comme code mort, si tout va bien l'optimiseur ne s'en rend pas compte) et pit

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

contre

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

alors on verra qu'ils courent à peu près à la même vitesse.

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
répondu Bergi 2017-05-23 11:33:16