【问题标题】:Curious JavaScript performance dependent on variable scope取决于变量范围的好奇 JavaScript 性能
【发布时间】:2016-03-05 08:28:17
【问题描述】:

在测试一个 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 引擎上出现这种情况?

【问题讨论】:

  • new Date().getTime() 是一个非常糟糕的性能测量工具。使用 Performance API 获得更准确的读数。
  • 本次测试使用内置的Date函数没有问题。
  • @MadaraUchiha 我更新了我的代码和测试结果以使用性能 API,它没有明显的效果。不过感谢您的提示!
  • 你怎么能确定解释器没有完全优化循环呢?这似乎很可能会产生三个数量级的差异。
  • @torazaburo 因为增加循环长度n 次按比例增加执行时间n 次。如果它已被优化,执行时间将是恒定的,接近 0。我将更新我的问题以反映这一点。

标签: javascript performance scope


【解决方案1】:

您可以通过在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  ...

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

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

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

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

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

【讨论】:

  • 也许 node.js 将包装函数放在那里 因为 这个问题的问题?因为确实 - 在 node.js 中运行我所有的示例都同样快
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    在技术 1 下查看这个 - http://www.webreference.com/programming/javascript/jkm3/index.html

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

    【讨论】:

    • 谢谢,这部分回答了我的问题。如果这是例如,我会接受你的回答。 2008 年(那是您写给定文章的时间),从那时起 JS 是一种完全解释的语言。然而,现在所有现代 JS 引擎,至少是我测试过的引擎,都将 JIT 编译为低级语言。此时命名空间卷积似乎不是问题。例如,C++ 没有这个,虽然我不确定这是否是一个公平的比较。我也会在 C# 上进行测试,它也是一种 JIT 编译语言,但遗憾的是 (幸运的是?) 它没有真正的全局变量。
    • 这似乎不太可能解释三个数量级的差异。
    • 我在 Chrome 上重新运行了上述测试。今天的结果是:[1] 1103 毫秒 [2] 1121 毫秒 [3] 350 毫秒。现在本地/本地选项的差异仅快 3 倍。
    猜你喜欢
    • 2018-09-19
    • 2020-04-10
    • 2014-11-22
    • 2020-02-07
    • 2012-12-23
    • 2011-05-10
    • 2011-07-07
    相关资源
    最近更新 更多