2016-03-05 33 views
4

当测试一个JavaScript项目的性能时,我注意到一个非常奇怪的行为 - 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."); 

的代码之间在本地和全球范围运行差异误差范围内,虽然火狐似乎并获得在局部范围运行的相当一致的20%的性能提升。

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

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

+3

'new Date()。getTime()'是一个非常糟糕的性能测量工具。使用Performance API来获得更准确的读数。 –

+0

使用内置Date函数进行此测试没有任何问题。 – Freddie

+0

@MadaraUchiha我更新了我的代码和测试结果以使用性能API,但没有明显的效果。感谢您的提示,但! –

回答

4

通过在Node.js下运行代码并在node命令行上传递--print_opt_code开关,您可以看到由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为此提供建议。

+1

也许node.js把包装函数放在那里*是因为这个问题中的问题?因为确实 - 运行我所有的示例在node.js中同样快 –

1

已根据技术1看看这个 - http://www.webreference.com/programming/javascript/jkm3/index.html

全局变量的性能下降,因为他们生活在一个人口稠密的命名空间。它们不仅与许多其他用户定义的数量和JavaScript变量一起存储,浏览器还必须区分全局变量和当前上下文中的对象的属性。当前上下文中的许多对象可以通过变量名来引用,而不是作为对象属性引用,如alert()与window.alert()同义。不利的一面是这种便利减慢了使用全局变量的代码。

+0

谢谢,这*部分*回答了我的问题。如果这是例如,我会接受你的回答2008年(这是你的文章写作时),从那时起JS是一个完全解释的语言。然而,现在所有现代JS引擎,至少是我测试过的引擎,都会将JIT编译降低到低级语言。命名空间卷积在这一点上似乎是一个非问题。例如C++没有这个,但我不确定这是否是一个公平的比较。我也会在C#上进行测试,这也是一种JIT编译的语言,但不幸的是*(幸运的是)*它没有真正的全局变量。 –

+0

这似乎不太可能解释三个数量级的差异。 – 2016-03-05 09:35:56

+0

我在Chrome上重新执行了上述测试。今天的结果是: [1] 1103 ms [2] 1121 ms [3] 350 ms。 现在的区别只是本地/本地选项的3倍。 –

3

全局命名空间中的变量将具有更差的性能,但不完全是因为@Freddie提到的原因。全局名称空间中的一个变量可能会被外部的某些东西改变,迫使解释器每次在循环中重新加载该值。使用局部变量,JIT引擎可以将循环优化到每次迭代几个机器周期,这在这里似乎正在发生。

+0

不错的观察和一个不错的答案! +1。 – Hrishi