JavaScript性能取决于变量范围

JavaScript性能取决于变量范围,javascript,performance,scope,Javascript,Performance,Scope,在测试一个JavaScript项目的性能时,我注意到一个非常特殊的行为——JavaScript成员访问性能似乎受到其所在范围的严重影响。我写了一些性能测试,结果有多个数量级的不同 我使用以下浏览器在Windows 10 64位上进行了测试: Google Chrome,49.0.2623.75 m版-使用 Mozilla Firefox,44.0.2版-使用 Microsoft Edge,版本25.10586-使用 以下是我进行的最相关的测试及其各自的结果: // Code running

在测试一个JavaScript项目的性能时,我注意到一个非常特殊的行为——JavaScript成员访问性能似乎受到其所在范围的严重影响。我写了一些性能测试,结果有多个数量级的不同

我使用以下浏览器在Windows 10 64位上进行了测试:

  • Google Chrome,49.0.2623.75 m版-使用
  • Mozilla Firefox,44.0.2版-使用
  • Microsoft Edge,版本25.10586-使用
以下是我进行的最相关的测试及其各自的结果:

// Code running on global scope, accessing a variable on global scope
// Google Chrome:   63000 ms.
// Mozilla Firefox: 57000 ms.
// Microsoft Edge:  21000 ms.
var begin = performance.now();
var i;
for(i = 0; i < 100000000; i++) { }
var end = performance.now();
console.log(end - begin + " ms.");


// Code running on local scope, accessing a variable on global scope
// Google Chrome:   61500 ms.
// Mozilla Firefox: 47500 ms.
// Microsoft Edge:  22000 ms.
var begin = performance.now();
var i;
(function() {
    for(i = 0; i < 100000000; i++) { }
})();
var end = performance.now();
console.log(end - begin + " ms.");

// Code running on local scope, accessing a variable on local scope
// Google Chrome:   50 ms.
// Mozilla Firefox: 28 ms.
// Microsoft Edge:  245 ms.
var begin = performance.now();
(function() {
    var i;
    for(i = 0; i < 100000000; i++) { }
})();
var end = performance.now();
console.log(end - begin + " ms.");
//在全局作用域上运行的代码,访问全局作用域上的变量
//谷歌浏览器:63000毫秒。
//Mozilla Firefox:57000毫秒。
//微软边缘:21000毫秒。
var begin=performance.now();
var i;
对于(i=0;i<100000000;i++){}
var end=performance.now();
console.log(结束-开始+“毫秒”);
//在局部作用域上运行的代码,访问全局作用域上的变量
//谷歌浏览器:61500毫秒。
//Mozilla Firefox:47500毫秒。
//微软边缘:22000毫秒。
var begin=performance.now();
var i;
(功能(){
对于(i=0;i<100000000;i++){}
})();
var end=performance.now();
console.log(结束-开始+“毫秒”);
//在局部作用域上运行的代码,访问局部作用域上的变量
//谷歌浏览器:50毫秒。
//Mozilla Firefox:28毫秒。
//微软边缘:245毫秒。
var begin=performance.now();
(功能(){
var i;
对于(i=0;i<100000000;i++){}
})();
var end=performance.now();
console.log(结束-开始+“毫秒”);
在本地范围和全局范围内运行的代码之间的差异在误差范围之内,尽管Firefox在本地范围内运行的性能似乎得到了相当一致的20%的提升

最大的惊喜是在本地范围内访问变量,在Chrome和Firefox上的速度是的1200到1600倍,在Edge上的速度是的90倍


在三种不同的浏览器/JavaScript引擎上,为什么会出现这种情况?

请在技术1-

全局变量的性能很慢,因为它们位于高度填充的命名空间中。它们不仅与许多其他用户定义的数量和JavaScript变量一起存储,浏览器还必须区分全局变量和当前上下文中对象的属性。当前上下文中的许多对象可以通过变量名而不是对象属性来引用,例如alert()与window.alert()同义。不利的一面是这种便利性降低了使用全局变量的代码的速度


全局名称空间中的变量的性能会差得多,但并不完全是因为@Freddie提到的原因。全局命名空间中的变量可能会被外部内容更改,从而迫使解释器每次通过循环重新加载该值。通过使用局部变量,JIT引擎可以将循环优化到每次迭代几个机器周期,这似乎就是这里发生的事情。

您可以看到V8 JavaScript引擎生成的实际机器代码(与Chrome中使用的相同)通过在
节点
命令行上的
--print_opt_code
开关下运行代码并传递代码。例如,如果将代码放入名为
test.js
的文件中,则可以运行:

node --print_opt_code test.js
在上一个示例中,V8能够将
i
变量放入
RAX
寄存器中,而不是将其保存在内存中。这是上面命令输出的代码的内部循环,还有一些额外的注释。(前后都有其他代码;这只是内部循环本身。)

请注意,
0x5f5e100
是以十六进制表示的
100000000

正如您所看到的,这是一个相当紧密的循环,只有几个指令。大部分代码是JavaScript代码的直接翻译;我唯一有点不确定的是地址97和104处的两条指令,如果满足某个条件,它们将退出循环

如果您使用其他版本的JavaScript代码运行类似的测试,您将看到更长的指令序列。请注意,Node将所有代码包装在它提供的包装函数中。因此,如果您想执行第一个示例中的操作,您可能需要像这样编写循环以获得类似的效果:

for(global.i = 0; global.i < 100000000; global.i++) { }
for(global.i=0;global.i<100000000;global.i++){

也许有一种方法可以告诉节点不要使用它的外包装函数;我对Node还不太熟悉,无法就此提出建议。

newdate()。getTime()
是一个非常糟糕的性能度量工具。使用性能API可以获得更准确的读数。使用内置日期函数进行此测试没有任何问题。@MadaraUchiha我更新了代码和测试结果以使用性能API,但没有明显的效果。谢谢你的提示!您如何确定解释器没有完全优化循环?这似乎很可能会产生三个数量级的差异。@torazaburo,因为增加循环长度
n
倍会成比例地增加执行时间
n
倍。如果对其进行了优化,则执行时间将保持不变,接近于0。我将更新我的问题以反映这一点。谢谢,这部分回答了我的问题。如果这是2008年(也就是你写文章的时候),我会接受你的答案,因为JS是一种完全解释的语言。然而,现在所有的现代JS引擎,至少是我测试过的那些,都使用JIT编译,直到一种低级语言。在这一点上,名称空间卷积似乎不是问题。例如C++没有这个,虽然我不确定这是否是公平的比较。我也会在C#上进行测试,C#也是一种JIT编译语言,但遗憾的是(幸运的是?)
for(global.i = 0; global.i < 100000000; global.i++) { }