Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does declaring a counter variable outside of a nested function make a loop 5x slower?

I was looking for some micro optimizations of some JavaScript legacy code I was revisiting and noticed that in most frequently called for loops, counters were declared once in the global scope, outside the functions using them. I was curious whether that was indeed an optimization therefore I have created the following test case in JavaScript:

var tmp = 0;

function test(){

    let j = 0;

    function letItBe(){

        for(j = 0; j < 1000; j++){
            tmp = Math.pow(j, 2);
        }
    }

    function letItNotBe(){
        for(let l = 0; l < 1000; l++){
            tmp = Math.pow(l, 2);
        }
    }

    console.time("let it be");
    for(var i =0; i < 10000; i++){

        letItBe();
    }
    console.timeEnd("let it be");


    console.time("let it not be");
    for(var i =0; i < 10000; i++){

        letItNotBe();
    }
    console.timeEnd("let it not be");
}

test();

What happens is that letItNotBe() runs significantly faster than letItBe(), in Chrome, Firefox and also NodeJS

Chrome: enter image description here

NodeJS:

enter image description here

Changing let with var makes no difference.

Initally my logic was that declaring a new counter variable every time a function is called would be indeed slower than if a variable is initially declared and then simply reset to 0. However, it turns out to be quite the oposite and the difference in execution time is quite substential.

My simple explanation is that when the counter variable is declared ouside the function using it, in some way the JS transpiler needs to refer to this variable. And since it is in the parent scope it takes more executions to reference it when incrementing. However that's just blind guessing.

Can anybody give any meaningful explanation why this is happening, since I need to refactor the code and give a meaningful explanation mysefl besides the test that I already have :) Thanks.

like image 539
Lys Avatar asked Mar 14 '18 09:03

Lys


People also ask

What is the name of the counter variable in the for loop?

i,j and k are the standard counter variables. By using them you imply the variables are used to keep loop count and nothing else.

Can we declare variable inside for loop in JavaScript?

If a variable is declared inside a loop, JavaScript will allocate fresh memory for it in each iteration, even if older allocations will still consume memory.

Should I use let in for loops?

A for loop's control variable is normally not constant (since in the normal case you update it in the "update" clause of the for ; if you don't, for may be the wrong loop to use), so you normally use let with it.

Can I store a for loop in a variable JavaScript?

Syntax of the for…of Loop in JavaScript First, you must specify a variable where the value will be stored for the current loop. Then, every time the loop iterates, the value of this variable will be updated. You can declare this variable using the “ var “, “ let ” or “ const ” keywords.


1 Answers

I've read a book High Performance JavaScript, the author explained this at Chapter 2 "Data Access" - Section "Managing Scope" - Part "Identifier Resolution Performance".

Identifier resolution isn’t free, as in fact no computer operation really is without some sort of performance overhead. 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 (optimizing JavaScript engines are capable of tuning this in certain situations).

...

The general trend across all browsers is that the deeper into the scope chain an identifier exists, the slower it will be read from or written to.

...

Given this information, it’s advisable to use local variables whenever possible to improve performance in browsers without optimizing JavaScript engines. A good rule of thumb is to always store out-of-scope values in local variables if they are used more than once within a function.

In your case, letItBe and letItNotBe work in the same way, using the same out-of-scope tmp variable, and both of them are closures.The only difference is the counter variables of for loops:

  • variable j is defined for function test(), it's 'out-of-scope' for function letItBe(), so executing letItBe() will cause the engine to do more works on identifier resolution
  • variable l is defined in scope of for loop (see let keyword in the for loop), so resolution is faster
like image 196
kite.js.org Avatar answered Oct 25 '22 03:10

kite.js.org