Why is using `let` inside a `for` loop so slow on Chrome?
Update: June 2018: Chrome now optimizes this much better than it did when this question and answer were first posted; there's no longer any appreciable penalty for using let
in the for
if you aren't creating functions in the loop (and if you are, the benefits are worth the cost).
Because a new i
is created for each iteration of the loop, so that closures created within the loop close over the i
for that iteration. This is covered by the specification in the algorithm for the evaluation of a for
loop body, which describes creating a new variable environment per loop iteration.
Example:
for (let i = 0; i < 5; ++i) { setTimeout(function() { console.log("i = " + i); }, i * 50);}// vs.setTimeout(function() { let j; for (j = 0; j < 5; ++j) { setTimeout(function() { console.log("j = " + j); }, j * 50); }}, 400);
That's more work. If you don't need a new See update above, no need to avoid it other than edge cases.i
for each loop, use let
outside the loop.
We can expect that now that everything but modules has been implemented, V8 will probably improve optimization of the new stuff, but it's not surprising that functionality should be initially prioritized over optimization.
It's great that other engines have already done the optimization, but the V8 team apparently just haven't got there yet. See update above.
MAJOR UPDATE.
Thought as yet not on the Chrome major release the new Ignition+Turbofan engines for Chrome Canary 60.0.3087 has solved the problem. Test show identical times for let
and var
declared loop variables.
Side note. My testing code uses Function.toString()
and failed on Canary because it returns "function() {"
not "function () {"
as past versions (easy fix using regexp) but a potencial problem for those that use Function.toSting()
Update Thanks to the user Dan. M who provide the link https://bugs.chromium.org/p/v8/issues/detail?id=4762 (and heads up) which has more on the issue.
Previous answer
Optimiser opted out.
This question has puzzled me for some time and the two answers are the obvious answers, but it made no sense as the time difference was too great to be the creation of a new scoped variable and execution context.
In an effort to prove this I found the answer.
Short answer
A for loop with a let statement in the declaration is not supported by the optimiser.
Chrome Version 55.0.2883.35 beta, Windows 10.
A picture worth a thousand words, and should have been the first place to look.
The relevant functions for the above profile
var time = [0,0]; // hold total timesfunction letInside(){ var start = performance.now(); for(let i = 0; i < 1e5; i += 1); // <- if you try this at home don't forget the ; time[0] += performance.now()-start; setTimeout(letOutside,10);}function letOutside(){ // this function is twice as quick as test on chrome var start = performance.now(); {let i; for(i = 0; i < 1e5; i += 1)} time[1] += performance.now()-start; setTimeout(displayResults,10);}
As Chrome is the major player and the blocked scoped variables for loop counters are everywhere, those who need performant code and feel that block scoped variables are important function{}(for(let i; i<2;i++}{...})//?WHY?
should consider for the time being the alternative syntax and declare the loop counter outside the loop.
I would like to say that the time difference is trivial, but in light of the fact that all code within the function is not optimized using for(let i...
should be used with care.
@T.J.Crowder already answered the title question, but I'll answer your doubts.
When I first encountered this I thought it was because of the newly created instance of i but the following shows this is not so.
Actually, it is because of the newly created scope for the i
variable. Which is not (yet) optimised away as it is more complicated than a simple block scope.
See the second code snippet as I have eliminated any possibility of the additional let declaration being optimised out with ini with random and then adding to indeterminate value of k.
Your additional let j
declaration in
{let i; for (i = 0; i < 1e3; i ++) {let j = Math.random(); j += i; k += j;}}// I'll ignore the `p` variable you had in your code
was optimised out. That's a pretty trivial thing to do for an optimiser, it can avoid that variable altogether by simplifying your loop body to
k += Math.random() + i;
The scope isn't really needed unless you create closures in there or use eval
or similar abominations.
If we introduce such a closure (as dead code, hopefully the optimiser doesn't realise that) and pit
{let i; for (i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}}
against
for (let i=0; i < 1e3; i++) { let j=Math.random(); k += j+i; function f() { j; }}
then we'll see that they run at about the same speed.