Curious JavaScript performance dependent on variable scope

家住魔仙堡 提交于 2019-12-07 09:04:58

问题


When testing the performance of one JavaScript project, I noticed a very peculiar behavior - JavaScript member access performance seems to be heavily influenced by the scope they are in. I wrote a few performance tests, and the results were different by multiple orders of magnitude.

I tested on Windows 10 64-bit, using these browsers:

  • Google Chrome, version 49.0.2623.75 m - uses the V8 JavaScript engine
  • Mozilla Firefox, version 44.0.2 - uses the SpiderMonkey JavaScript engine
  • Microsoft Edge, version 25.10586 - uses the Chakra JavaScript engine

Here are the most relevant tests I ran and their respective results:

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

The difference between code running in local and global scopes was within the margin of error, although Firefox did seem to get a pretty consistent 20% performance boost running in local scope.

The biggest surprise was accessing a variable on a local scope, it was 1200 to 1600 times faster on Chrome and Firefox, and 90 times faster on Edge.

Why would this be the case, on three different browsers / JavaScript engines?


回答1:


You can see the actual machine code generated by the V8 JavaScript engine (same as used in Chrome) by running your code under Node.js and passing the --print_opt_code switch on the node command line. For example if you put your code in a file called test.js you can run:

node --print_opt_code test.js

In your last example, V8 is able to put the i variable in the RAX register instead of keeping it in memory. Here's the inner loop from the code printed out by the above command, with some extra notes. (There is additional code before and after; this is just the inner loop itself.)

 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  ...

Note that 0x5f5e100 is 100000000 represented in hexadecimal.

As you can see, this is a fairly tight loop with only a few instructions. Most of the code is a direct translation of the JavaScript code; the only thing I'm a bit unsure about are the two instructions at addresses 97 and 104 that bail out of the loop if a certain condition is met.

If you run similar tests with your other versions of JavaScript code you will see much lengthier instruction sequences. Just be aware that Node wraps all of your code inside a wrapper function that it provides. So if you want to do something like your first example you may need to write the loop like this to get a similar effect:

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

Perhaps there is a way to tell Node to not use its outer wrapper function; I'm not familiar enough with Node to advise on that.




回答2:


The variable in the global namespace will have much poorer performance, but not precisely for the reasons that @Freddie mentions. A variable in the global namespace could potentially be changed by something external, forcing the interpreter to reload the value each time through the loop. Using a local variable, the JIT engine can optimize the loop down to a few machine cycles per iteration, which is what seems to be happening here.




回答3:


Have a look at this under Technique 1 - http://www.webreference.com/programming/javascript/jkm3/index.html

Global variables have slow performance because they live in a highly-populated namespace. Not only are they stored along with many other user-defined quantities and JavaScript variables, the browser must also distinguish between global variables and properties of objects that are in the current context. Many objects in the current context can be referred to by a variable name rather than as an object property, such as alert() being synonymous with window.alert(). The down side is this convenience slows down code that uses global variables.



来源:https://stackoverflow.com/questions/35811885/curious-javascript-performance-dependent-on-variable-scope

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!