Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Curious JavaScript performance dependent on variable scope

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?

like image 794
Gediminas Masaitis Avatar asked Feb 08 '23 13:02

Gediminas Masaitis


1 Answers

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.

like image 65
Michael Geary Avatar answered Feb 11 '23 00:02

Michael Geary