在测试一个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引擎上?
答案 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 ...
请注意,0x5f5e100
为100000000
,以十六进制表示。
正如您所看到的,这是一个相当紧凑的循环,只有几条指令。大多数代码都是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()同义。缺点是这种方便性会减慢使用全局变量的代码。