为什么在 Chrome 上的 `for` 循环中使用 `let` 这么慢?
Why is using `let` inside a `for` loop so slow on Chrome?
主要更新。
还没有想到 Chrome 主要版本 Chrome Canary 59 的新 Ignition+Turbofan engines 已经解决了这个问题。测试显示 let
和 var
声明的循环变量的时间相同。
原始(现在没有实际意义)问题。
当在 Chrome 的 for
循环中使用 let
时,与将变量移出循环范围相比,它运行得非常慢。
for(let i = 0; i < 1e6; i ++);
花费的时间是
的两倍
{ let i; for(i = 0; i < 1e6; i ++);}
这是怎么回事?
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()
当我第一次遇到这个问题时,我以为是因为新创建的 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()
更新: 2018 年 6 月:Chrome 现在对此问题和答案首次发布时进行了优化;如果您不在循环中创建函数(如果您在循环中创建函数,那么收益是值得的)。
因为会为循环的每次迭代创建一个新的 i
,因此在循环中创建的闭包会在 i
的那次迭代 上关闭. evaluation of a for
loop body 的算法规范涵盖了这一点,它描述了在每次循环迭代时创建一个新的变量环境。
示例:
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);
还有更多工作要做。 如果您不需要为每个循环创建新的 i
,请在循环外使用 let
。 请参阅上面的更新,无需避免除了边缘情况。
我们可以预期,现在除了模块之外的所有内容都已实现,V8 可能会改进新内容的优化,但功能最初应优先于优化也就不足为奇了。
很高兴其他引擎已经完成了优化,但 V8 团队显然还没有做到这一点。 请参阅上面的更新。
@T.J.Crowder已经回答了题主的问题,我来解答你的疑惑。
When I first encountered this I thought it was because of the newly created instance of i but the following shows this is not so.
其实是因为i
变量新建了作用域。 .
尚未(尚未)优化
See the second code snippet as I have eliminated any possibility of the additional let declaration being optimised out with ini with random and then adding to indeterminate value of k.
您在
中的额外 let j
声明
{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
被优化掉了。对于优化器来说,这是一件非常微不足道的事情,它可以通过将循环体简化为
来完全避免该变量
k += Math.random() + i;
除非您在其中创建闭包或使用 eval
或类似的可憎行为,否则实际上并不需要作用域。
如果我们引入这样一个闭包(作为死代码,希望优化器没有意识到这一点)和坑
{let i; for (i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}}
反对
for (let i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}
然后我们会看到它们 运行 的速度大致相同。
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();
主要更新。
还没有想到 Chrome 主要版本 Chrome Canary 60.0.3087 的新 Ignition+Turbofan engines 已经解决了这个问题。测试显示 let
和 var
声明的循环变量的时间相同。
旁注。 我的测试代码使用 Function.toString()
并在 Canary 上失败,因为它 returns "function() {"
而不是 "function () {"
过去的版本(使用正则表达式很容易修复)但是对于那些使用 Function.toSting()
的人来说是一个潜在的问题
更新 感谢用户 Dan. M who provide the link https://bugs.chromium.org/p/v8/issues/detail?id=4762(请注意),他对这个问题有更多的了解。
上一个回答
优化器选择退出。
这个问题让我困惑了一段时间,两个答案是显而易见的答案,但是由于时间差异太大而无法创建新的作用域变量和执行上下文,所以没有任何意义。
为了证明这一点,我找到了答案。
简答
优化器不支持声明中带有 let 语句的 for 循环。
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);
}
由于Chrome是主要参与者,循环计数器的块作用域变量无处不在,那些需要高性能代码并认为块作用域变量很重要的人应该暂时考虑function{}(for(let i; i<2;i++}{...})//?WHY?
替代语法并在循环外声明循环计数器。
我想说时间差异是微不足道的,但鉴于函数内的所有代码都没有使用 for(let i...
进行优化,应谨慎使用。
主要更新。
还没有想到 Chrome 主要版本 Chrome Canary 59 的新 Ignition+Turbofan engines 已经解决了这个问题。测试显示 let
和 var
声明的循环变量的时间相同。
原始(现在没有实际意义)问题。
当在 Chrome 的 for
循环中使用 let
时,与将变量移出循环范围相比,它运行得非常慢。
for(let i = 0; i < 1e6; i ++);
花费的时间是
的两倍{ let i; for(i = 0; i < 1e6; i ++);}
这是怎么回事?
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()
当我第一次遇到这个问题时,我以为是因为新创建的 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()
更新: 2018 年 6 月:Chrome 现在对此问题和答案首次发布时进行了优化;如果您不在循环中创建函数(如果您在循环中创建函数,那么收益是值得的)。
因为会为循环的每次迭代创建一个新的 i
,因此在循环中创建的闭包会在 i
的那次迭代 上关闭. evaluation of a for
loop body 的算法规范涵盖了这一点,它描述了在每次循环迭代时创建一个新的变量环境。
示例:
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);
还有更多工作要做。 如果您不需要为每个循环创建新的 请参阅上面的更新,无需避免除了边缘情况。i
,请在循环外使用 let
。
我们可以预期,现在除了模块之外的所有内容都已实现,V8 可能会改进新内容的优化,但功能最初应优先于优化也就不足为奇了。
很高兴其他引擎已经完成了优化,但 V8 团队显然还没有做到这一点。 请参阅上面的更新。
@T.J.Crowder已经回答了题主的问题,我来解答你的疑惑。
When I first encountered this I thought it was because of the newly created instance of i but the following shows this is not so.
其实是因为i
变量新建了作用域。
See the second code snippet as I have eliminated any possibility of the additional let declaration being optimised out with ini with random and then adding to indeterminate value of k.
您在
中的额外let j
声明
{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
被优化掉了。对于优化器来说,这是一件非常微不足道的事情,它可以通过将循环体简化为
来完全避免该变量k += Math.random() + i;
除非您在其中创建闭包或使用 eval
或类似的可憎行为,否则实际上并不需要作用域。
如果我们引入这样一个闭包(作为死代码,希望优化器没有意识到这一点)和坑
{let i; for (i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}}
反对
for (let i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}
然后我们会看到它们 运行 的速度大致相同。
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();
主要更新。
还没有想到 Chrome 主要版本 Chrome Canary 60.0.3087 的新 Ignition+Turbofan engines 已经解决了这个问题。测试显示 let
和 var
声明的循环变量的时间相同。
旁注。 我的测试代码使用 Function.toString()
并在 Canary 上失败,因为它 returns "function() {"
而不是 "function () {"
过去的版本(使用正则表达式很容易修复)但是对于那些使用 Function.toSting()
更新 感谢用户 Dan. M who provide the link https://bugs.chromium.org/p/v8/issues/detail?id=4762(请注意),他对这个问题有更多的了解。
上一个回答
优化器选择退出。
这个问题让我困惑了一段时间,两个答案是显而易见的答案,但是由于时间差异太大而无法创建新的作用域变量和执行上下文,所以没有任何意义。
为了证明这一点,我找到了答案。
简答
优化器不支持声明中带有 let 语句的 for 循环。
一张千言万语的图,应该是第一个看的
上述配置文件的相关功能
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);
}
由于Chrome是主要参与者,循环计数器的块作用域变量无处不在,那些需要高性能代码并认为块作用域变量很重要的人应该暂时考虑function{}(for(let i; i<2;i++}{...})//?WHY?
替代语法并在循环外声明循环计数器。
我想说时间差异是微不足道的,但鉴于函数内的所有代码都没有使用 for(let i...
进行优化,应谨慎使用。