为什么在`for`循环中使用`let`在Chrome上这么慢?

Bli*_*n67 23 javascript performance google-chrome let ecmascript-6

主要更新.

考虑到Chrome主要版本尚未发布,Chrome Canary 59 的新型Ignition + Turbofan引擎已经解决了这个问题.测试显示相同的时间letvar声明的循环变量.


原始(现在是静音)问题.

当使用letforChrome上循环运行速度非常缓慢,相比于移动变量外刚内循环的范围.

for(let i = 0; i < 1e6; i ++); 
Run Code Online (Sandbox Code Playgroud)

需要两倍的时间

{ let i; for(i = 0; i < 1e6; i ++);}
Run Code Online (Sandbox Code Playgroud)

到底是怎么回事?

Snippet演示了差异,只影响Chrome,只要我记得Chrome支持,就一直如此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()
Run Code Online (Sandbox Code Playgroud)

当我第一次遇到这个时,我认为这是因为新创建的i实例,但以下显示并非如此.

请参阅代码片段,因为我已经消除了使用ini随机优化附加let声明的任何可能性,然后添加到k的不确定值.

我还添加了第二个循环计数器 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()
Run Code Online (Sandbox Code Playgroud)

T.J*_*der 23

更新: 2018年6月:Chrome现在比这个问题和答案首次发布时更好地优化了这一点; 如果你没有在循环中创建函数,那么在使用let中不会有任何明显的损失for(如果你是,那么这些好处是值得的).


因为i为循环的每次迭代创建了一个new ,所以在循环中创建的闭包关闭了该i 迭代的闭包.这由用于评估for循环体的算法中的规范涵盖,其描述了每循环迭代创建新的变量环境.

例:

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);
Run Code Online (Sandbox Code Playgroud)

那是更多的工作.如果i每个循环不需要new ,请let在循环外部使用. 请参阅上面的更新,除了边缘情况之外无需避免它.

我们可以预期,现在除了模块之外的所有东西都已经实现了,V8可能会改进新东西的优化,但是功能应该首先优先于优化并不奇怪.

很高兴其他引擎已经完成了优化,但V8团队显然还没有到达那里.见上面的更新.


Bli*_*n67 5

主要更新.

考虑到Chrome主要版本还没有针对Chrome Canary 60.0.3087 的新型Ignition + Turbofan 引擎解决了这个问题.测试显示相同的时间letvar声明的循环变量.

边注.我的测试代码Function.toString()在Canary上使用和失败,因为它"function() {"不像"function () {"以前的版本那样返回(使用regexp轻松修复)但对于那些使用它的人来说是一个潜在的问题Function.toSting()

更新感谢用户Dan.中号谁提供的链接https://bugs.chromium.org/p/v8/issues/detail?id=4762(和单挑),其中有更多的问题.


以前的答案

Optimiser选择退出.

这个问题让我困惑了一段时间,这两个答案是明显的答案,但由于时间差太大而无法创建新的范围变量和执行上下文,因此没有任何意义.

为了证明这一点,我找到了答案.

简短的回答

优化器不支持声明中带有let语句的for循环.

这两个功能的Chrome配置文件显示慢功能未优化 Chrome版本55.0.2883.35测试版,Windows 10.

一张价值千言万语的图片,应该是第一个看的地方.

上述个人资料的相关功能

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);
}
Run Code Online (Sandbox Code Playgroud)

由于Chrome是主要的参与者,并且循环计数器的阻止范围变量无处不在,那些需要高性能代码并认为块范围变量很重要的人function{}(for(let i; i<2;i++}{...})//?WHY?应该考虑暂时替代语法并在循环外声明循环计数器.

我想说的是时间差是微不足道的,但是考虑到函数中的所有代码都没有经过优化使用for(let i...应该谨慎使用.