skip to Main Content

I recently read "High Performance JavaScript" (2010) which states:

The deeper into the execution context’s scope chain an identifier exists, the slower it is to access for both reads and writes. Consequently, local variables are always the fastest to access inside of a function, whereas global variables will generally be the slowest. Keep in mind that global variables always exist in the last variable object of the execution context’s scope chain, so they are always the furthest away to resolve.

Given the substantial optimizations in JavaScript engines over the past decade, I wonder is this statement still accurate in 2024?

let globalVar = 0;

function testVariables() {
    let localVar = 0;
    let globalStartTime = performance.now();

    for (let i = 0; i < 5000000; i++) {
        globalVar += 1;
    }
    let globalEndTime = performance.now();

    let localStartTime = performance.now();

    for (let i = 0; i < 5000000; i++) {
        localVar += 1;
    }
    let localEndTime = performance.now();

    console.log(`Global variable access time: ${globalEndTime - globalStartTime} ms`);
    console.log(`Local variable access time: ${localEndTime - localStartTime} ms`);
}

testVariables();

The output is:
Global variable access time: 25.80000001192093 ms
Local variable access time: 11.400000035762787 ms

It is surely that global variables take more time to be accessed than local variables, but I am still not sure that whether scope chain traversal is the main reason behind this. Perhaps there are other reasons?

2

Answers


  1. I think another reason is that if you have variable in the global namespace, then external can change that variable and it will force the interpreter to reload the value each time through the loop.
    Hence makes it slower than local variable

    Login or Signup to reply.
  2. When you access a variable in an outer scope (not always global, an engine should make a lookup), so it’s slower. Not sure why the global scope isn’t updated only after the loop completes, since there’re no reads in the global scope possible until the loop completes (maybe it’s not possible to optimize).

    With object references it’s different, since no object reference is mutated and objects are allocated on the heap, so no actual difference when you mutate them.

    And benchmarking:

    ` Chrome/125
    -------------------------------------------------------------------------------------------
    >                      n=5000       |     n=50000      |    n=500000     |    n=5000000    
    local             ■ 1.00x x100k 374 | ■ 1.00x x10k 343 | ■ 1.00x x1k 268 | ■ 1.00x x100 268
    object argument     1.44x x100k 539 |   1.40x x10k 479 |   1.43x x1k 384 |   1.37x x100 368
    local object        1.16x x100k 435 |   1.30x x10k 445 |   1.46x x1k 392 |   1.37x x100 368
    global object       1.28x x100k 479 |   1.40x x10k 480 |   1.36x x1k 364 |   1.40x x100 376
    global              1.71x x100k 639 |   1.72x x10k 590 |   2.00x x1k 536 |   2.05x x100 550
    outer scope         1.71x x100k 641 |   1.87x x10k 642 |   2.42x x1k 648 |   2.45x x100 657
    ------------------------------------------------------------------------------------------- `
    

    Open in the playground

    let globalVar = 0;
    const globalObj = {var: 0};
    let $length = 5000;
    
    // @benchmark global
    function loopVarG(){
      for(let i=0;i<$length;i++){
        globalVar += 1;
      }
    }
    // @run
    loopVarG();
    
    // @benchmark outer scope
    function loopVarOS(){
      let localVar = 0;
    
      function loopVarOS(){
        for(let i=0;i<$length;i++){
          localVar += 1;
        }
      }
    
      loopVarOS();
    }
    // @run
    loopVarOS();
    
    // @benchmark local
    function loopVar(){
      let localVar = 0;
      for(let i=0;i<$length;i++){
        localVar += 1;
      }
    }
    // @run
    loopVar();
    
    // @benchmark global object
    function loopVarGO(){
      for(let i=0;i<$length;i++){
        globalObj.var += 1;
      }
    }
    // @run
    loopVarGO();
    
    // @benchmark local object
    function loopVarO(){
      const localObj = {var: 0};
      for(let i=0;i<$length;i++){
        localObj.var += 1;
      }
    }
    // @run
    loopVarO();
    
    // @benchmark object argument
    function loopVarOA(localObj){
      for(let i=0;i<$length;i++){
        localObj.var += 1;
      }
    }
    // @run
    loopVarOA(globalObj);
    
    /*@skip*/ fetch('https://cdn.jsdelivr.net/gh/silentmantra/benchmark/loader.js').then(r => r.text().then(eval));
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search