为什么在嵌套函数外声明一个计数器变量会使循环变慢 5 倍?
Why does declaring a counter variable outside of a nested function make a loop 5x slower?
我正在寻找一些我正在重新访问的 JavaScript 遗留代码的微优化,并注意到在最常调用的 for 循环中,计数器在全局范围内声明一次,在使用它们的函数之外。我很好奇这是否确实是一种优化,因此我在 JavaScript:
中创建了以下测试用例
var tmp = 0;
function test(){
let j = 0;
function letItBe(){
for(j = 0; j < 1000; j++){
tmp = Math.pow(j, 2);
}
}
function letItNotBe(){
for(let l = 0; l < 1000; l++){
tmp = Math.pow(l, 2);
}
}
console.time("let it be");
for(var i =0; i < 10000; i++){
letItBe();
}
console.timeEnd("let it be");
console.time("let it not be");
for(var i =0; i < 10000; i++){
letItNotBe();
}
console.timeEnd("let it not be");
}
test();
在 Chrome、Firefox 和 NodeJS
中,letItNotBe()
的运行速度明显快于 letItBe()
Chrome:
NodeJS:
将 let 更改为 var 没有区别。
最初我的逻辑是,每次调用函数时都声明一个新的计数器变量确实比最初声明一个变量然后简单地重置为 0 慢。然而,事实证明这是完全相反的执行时间的差异非常大。
我的简单解释是,当在使用它的函数之外声明计数器变量时,JS 转换器需要以某种方式引用该变量。因为它在父范围内,所以在递增时需要更多的执行来引用它。然而,这只是盲目猜测。
任何人都可以给出任何有意义的解释为什么会发生这种情况,因为除了我已经拥有的测试之外,我还需要重构代码并给出有意义的解释 mysefl :) 谢谢。
我读过一本书 High Performance JavaScript,作者在第 2 章 "Data Access" - 第 "Managing Scope" 部分 - "Identifier Resolution Performance".
部分对此进行了解释
Identifier resolution isn’t free, as in fact no computer operation
really is without some sort of performance overhead. The deeper into
the execution context’s scope chain an identifier exists, the slower
it is to access for both reads and writes. Consequently, local
variables are always the fastest to access inside of a function,
whereas global variables will generally be the slowest (optimizing
JavaScript engines are capable of tuning this in certain situations).
...
The general trend across all browsers is that the deeper into the
scope chain an identifier exists, the slower it will be read from or
written to.
...
Given this information, it’s advisable to use local variables whenever
possible to improve performance in browsers without optimizing
JavaScript engines. A good rule of thumb is to always store
out-of-scope values in local variables if they are used more than once
within a function.
在你的情况下,letItBe
和 letItNotBe
的工作方式相同,使用相同的 out-of-scope tmp
变量,并且它们都是 closures.The 唯一的区别是 for
循环的计数器变量:
- 变量
j
是为函数test()
定义的,它是为函数letItBe()
定义的'out-of-scope',所以执行letItBe()
会导致引擎做更多的工作关于标识符解析
- 变量
l
定义在for
循环的范围内(参见let keyword in the for loop),因此解析速度更快
我正在寻找一些我正在重新访问的 JavaScript 遗留代码的微优化,并注意到在最常调用的 for 循环中,计数器在全局范围内声明一次,在使用它们的函数之外。我很好奇这是否确实是一种优化,因此我在 JavaScript:
中创建了以下测试用例var tmp = 0;
function test(){
let j = 0;
function letItBe(){
for(j = 0; j < 1000; j++){
tmp = Math.pow(j, 2);
}
}
function letItNotBe(){
for(let l = 0; l < 1000; l++){
tmp = Math.pow(l, 2);
}
}
console.time("let it be");
for(var i =0; i < 10000; i++){
letItBe();
}
console.timeEnd("let it be");
console.time("let it not be");
for(var i =0; i < 10000; i++){
letItNotBe();
}
console.timeEnd("let it not be");
}
test();
在 Chrome、Firefox 和 NodeJS
中,letItNotBe()
的运行速度明显快于 letItBe()
Chrome:
NodeJS:
将 let 更改为 var 没有区别。
最初我的逻辑是,每次调用函数时都声明一个新的计数器变量确实比最初声明一个变量然后简单地重置为 0 慢。然而,事实证明这是完全相反的执行时间的差异非常大。
我的简单解释是,当在使用它的函数之外声明计数器变量时,JS 转换器需要以某种方式引用该变量。因为它在父范围内,所以在递增时需要更多的执行来引用它。然而,这只是盲目猜测。
任何人都可以给出任何有意义的解释为什么会发生这种情况,因为除了我已经拥有的测试之外,我还需要重构代码并给出有意义的解释 mysefl :) 谢谢。
我读过一本书 High Performance JavaScript,作者在第 2 章 "Data Access" - 第 "Managing Scope" 部分 - "Identifier Resolution Performance".
部分对此进行了解释Identifier resolution isn’t free, as in fact no computer operation really is without some sort of performance overhead. The deeper into the execution context’s scope chain an identifier exists, the slower it is to access for both reads and writes. Consequently, local variables are always the fastest to access inside of a function, whereas global variables will generally be the slowest (optimizing JavaScript engines are capable of tuning this in certain situations).
...
The general trend across all browsers is that the deeper into the scope chain an identifier exists, the slower it will be read from or written to.
...
Given this information, it’s advisable to use local variables whenever possible to improve performance in browsers without optimizing JavaScript engines. A good rule of thumb is to always store out-of-scope values in local variables if they are used more than once within a function.
在你的情况下,letItBe
和 letItNotBe
的工作方式相同,使用相同的 out-of-scope tmp
变量,并且它们都是 closures.The 唯一的区别是 for
循环的计数器变量:
- 变量
j
是为函数test()
定义的,它是为函数letItBe()
定义的'out-of-scope',所以执行letItBe()
会导致引擎做更多的工作关于标识符解析 - 变量
l
定义在for
循环的范围内(参见let keyword in the for loop),因此解析速度更快