好奇的JavaScript性能取决于变量范围

时间:2016-03-05 08:28:18

标签: javascript performance scope

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

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

以下是我运行的最相关测试及其各自的结果:

// 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.");

在本地范围和全局范围内运行的代码之间的差异在误差范围内,虽然Firefox确实在本地范围内实现了相当稳定的20%性能提升

最大的惊喜是在本地范围内访问变量,在Chrome和Firefox上强> 1200到1600倍,在Edge上快了90倍。

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

3 个答案:

答案 0 :(得分:4)

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

node --print_opt_code test.js

在上一个示例中,V8能够将i变量放在RAX寄存器中,而不是将其保存在内存中。这是来自上述命令打印出的代码的内部循环,带有一些额外的注释。 (之前和之后还有其他代码;这只是内部循环本身。)

 84  33c0           xorl rax,rax                 ; i = 0
 86  3d00e1f505     cmp rax, 0x5f5e100           ; compare i with 100000000
 91  0f8d12000000   jge 115                      ; exit loop if i >= 100000000
 97  493ba548080000 REX.W cmpq rsp, [r13+0x848]  ; check for bailout?
104  0f8246000000   jc 180                       ; bailout if necessary
110  83c001         addl rax, 0x1                ; i++
113  ebe3           jmp 86                       ; back to top of loop
115  ...

请注意,0x5f5e100100000000,以十六进制表示。

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

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

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

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

答案 1 :(得分:3)

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

答案 2 :(得分:1)

在技术1 - http://www.webreference.com/programming/javascript/jkm3/index.html

下查看此内容
  

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

相关问题