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:
NodeJS:
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.
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.
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.
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.
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.
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:
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 resolutionl
is defined in scope of for
loop (see let keyword in the for loop), so resolution is fasterIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With