18

I read that it is advised to optimize loops in JavaScript by not reading the length attribute of an array every iteration in the loop header.

So, we should rather do this:

var names = ['George','Ringo','Paul','John'];
for(var i=0,j=names.length;i<j;i++){// Read array length once and assign it to a variable
    doSomeThingWith(names[i]);
}

instead of this:

var names = ['George','Ringo','Paul','John'];
for(var i=0;i<names.length;i++){
    doSomeThingWith(names[i]);
}

However, I created a small testcase to compare the two techniques, but sometimes the first case was faster and sometimes the second one.

Which version would you recommend?

Paul D. Waite
  • 96,640
  • 56
  • 199
  • 270
fabb
  • 11,660
  • 13
  • 67
  • 111

8 Answers8

24

First, I should say that this answer is written in 2011 and these things change over time (as browser interpreters optimize more and more things) so if you really want to know the current state of the world, you have to run tests on current browsers.

Run your own jsperf test on any version of IE. There you will see a consistent difference between the two methods or many other old browsers. You apparently only ran it on Chrome which is so fast and so optimized that there is a negligible difference between the two methods. On IE9 (which is likely way better than IE7 and IE8), the method which pre-caches the length is 31% faster.

A jsperf test designed for this question gives quantitative results on this question. In questions like this one should just go to jsperf to see what the real difference is rather than so much speculation.

It shows a difference in the browsers I tried that ranges from almost no difference to a pretty sizable difference depending upon the browser. In Chrome, there's almost no difference. In IE9, storing the length first is almost 50% faster.

Now, whether this speed difference matters to your scripts depends on the specific code. If you had a huge array that you were looping through frequently, it could make a meaningful difference in some browsers to use this form:

for (var i = 0, len = list.length; i < len; i++) {
    // do code here
} 

In a slightly different test case when using live pseudo arrays returned by some DOM functions, there was still a difference in speed, but not as magnified (I expected the difference to be greater on DOM pseudo live arrays, but it wasn't).

In practice, I tend to use the short version (less typing) when I don't think my section of code is speed critical and/or the array is not large and I would use the longer version that pre-caches the length if I am consciously thinking about speed or the array is huge or I'm doing a lot of iterations over the same array.

There are a couple other programming reasons to pre-cache the length. If you will be adding elements to the end of the array during the loop and you don't want to the loop to iterate over those newly added elements, then you will NEED to pre-load the length and only iterate over the initially present elements.

for (var i = 0, len = list.length; i < len; i++) {
    if (list[i] == "whatever") {
        list.push("something");
    }
} 

Keep in mind that browsers are continually evolving and adding more and more optimizations so an optimization that shows great benefit in 2011 may be essentially built into a more modern browser in the future so the hand coded optimization is no longer needed. So, if you're trying to optimize something for today's performance, you have to test in today's browsers, you can't just rely on things you read that may be a few years old.

Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    I added a second revision to your jsperf with a couple of more tests... http://jsperf.com/loop-iteration-length-comparison-variations/2 (just to satisfy my own interests!) – Matt Aug 07 '11 at 18:34
  • @Matt - I would have expected your versions to be faster, but they are not (in Chrome or IE9). I wonder why? – jfriend00 Aug 07 '11 at 18:38
  • I thought the same. In fact, I could have sworn I read somewhere that it was... hence my curiosity :P. In IE9 I found the runs were pretty similar, whereas in Chrome the `for` loops massively out-performed the `while` loop... I can only assume here that Chrome optimize for the `for` loop. – Matt Aug 07 '11 at 18:41
13

This advice was always a micro-optimization at best, and with all of the work being done on the speed of Javascript engines, it's unlikely to be a measurable difference any more. Perhaps somewhere in a very long very tight loop it might make a difference, but I doubt it.

For some reason, programmers tend to focus on speed above all else, even when it is unwarranted. Think about correctness, then readability.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • Still shows up to a 50% difference in IE9 as measured in the jspref test in my answer. Sometimes even that won't be relevant, but sometimes it will be (iterating very large array or doing lots of repeated iterations or in something that is very speed critical, etc...). I wouldn't just dismiss it for all situations. – jfriend00 Aug 07 '11 at 17:38
  • 2
    50% of what? Do you have anything real happening in the loop? I still claim that the chances are very small that this difference will matter at all to an actual Javascript program. – Ned Batchelder Aug 07 '11 at 18:01
  • 1
    You can look at my jsperf in my answer to see exactly what it measures. That's why quantitative tests are useful. There's no conjecture, it's all spelled out right in the test. My point was that there are some situations where the performance difference is relevant so it shouldn't be just dismissed as always irrelevant. The strong programmer should be trained to recognize when a little extra work to increase performance is worth it and when it is not. Performance of loops is not always irrelevant in JS. – jfriend00 Aug 07 '11 at 18:06
  • I say to think about your requirements. Then, implement the simplest (correct and readable) solution that meets your requirements. Sometimes good the best loop performance is an important requirement. In those cases, it may be useful to pre-cache the length. We are, after all, only talking about <10 more chars of code and no additional lines of code. – jfriend00 Aug 07 '11 at 18:10
  • @jfriend00: I see the jsperf: your loop body is nearly empty. Can you explain with the jsperf numbers mean? I didn't see an explanation of them. In any case, I agree with your answer: there are other things to think about first, and usually this optimization won't make a difference. – Ned Batchelder Aug 07 '11 at 18:15
  • Here an explanations of what jsPerf does: http://stackoverflow.com/questions/4986245/how-does-jsperf-work/4996963#4996963 and – jfriend00 Aug 07 '11 at 18:25
  • 1
    Yes, the loop body is nearly empty. If you're trying to measure a particular aspect in a test, you generally want to isolate that issue as best as possible. How much this loop difference is relevant in your app does depend upon how much time the rest of your loop is taking. The raw loop time may be most of your execution time or almost none of it depending upon what's going on in the loop. – jfriend00 Aug 07 '11 at 18:29
5

I would recommend the second:

var names = ['George','Ringo','Paul','John'];
for (var i = 0; i < names.length; i++) {
  doSomeThingWith(names[i]);
}

because it is more concise and more idiomatic. You won't ever need to use the first unless you are doing some absurd micro-optimization.

Peter Olson
  • 139,199
  • 49
  • 202
  • 242
  • 4
    See the jsperf tests in my answer below. It's not a ridiculous micro-optimization in some browsers (IE9 can be as much as 50% speed difference) so it depends upon the exact context. I wouldn't dismiss it as never relevant. It depends upon what operation is being done on how relevant the speed difference is. – jfriend00 Aug 07 '11 at 17:36
  • "because it is more concise and more idiomatic" - it's just a question of preference... and you put preference over efficiency – gion_13 Aug 08 '11 at 11:34
  • @gion_13 When writing readable code, yes. This sort of thing is part of YAGNI: it's an unnecessary optimization that is very unlikely to be the bottleneck in your code and it makes it more tiresome to read. If you are writing something demanding that your code is that optimized for performance, you can change it later. – Peter Olson Aug 08 '11 at 14:22
  • I really do not see how separating the declarations from the logic can be considered a "bottleneck". Sorry, I don't mean to argue, we clearly have different opinions. – gion_13 Aug 08 '11 at 14:37
5

As a general rule, caching the "stop value" of a loop (in your case names.length) is only valuable if it is a calculated value. For the array in question, it is just a lookup so you will gain little by caching it.

DwB
  • 37,124
  • 11
  • 56
  • 82
3

I'd recommend

var names = ['George','Ringo','Paul','John'];
var length = names.length;
for(var i=0;i<length;i++){
    doSomeThingWith(names[i]);
}
Paul D. Waite
  • 96,640
  • 56
  • 199
  • 270
genesis
  • 50,477
  • 20
  • 96
  • 125
3

Define "really necessary".
If you loop over an array of 4 elements, i don't think that even IE would mind, but have in mind that you maybe have to loop through some dom elements; say that you have a list (ul) with 1.000.000 (or more) entrances (li). I think that declaring an extra variable would save you checking the length property of that ul a milion times.
Maybe I exaggerated a bit with the million part, but take a look at the test results on only 10000 li's.
The optimised loop was almost a hundred times faster than the "normal" one.

My conclusion: optimise your loops... it can't harm you (or your code or your browser).

gion_13
  • 41,171
  • 10
  • 96
  • 108
  • Wow, your testcase really shows a massive difference. So when accessing DOM with .childElementCount or similar the optimized version is definitely better. – fabb Aug 08 '11 at 10:53
2

2017 Updated answer

You should use the optimized/best practice way.

In your exact example: it is so trivial, that it doesn’t matter. Even at 50% performance difference, as stated by @jfriend00, it doesn’t mean much. CPUs (to include current smart phones) can do millions of calculations per second. Meaning that the fraction of a millisecond just won’t even register to the user, which is in line with what @Ned Batchelder posted.

However, coding should not be about what you can get away with. That said, as @DwB said, the “…stop value…only valuable if it is a calculated value.” With that in mind, the following code gives an example of a time-wasting function to return a stop value. Here it becomes apparent just how different the speed is. Multiply the potential shortcomings across a server, complex client-side code, and other intensive calculations, and you will improve the user experience by using best practices.

  var eCount = document.getElementById("loopCount");
  var waitDiv = document.getElementById("waitDiv");
  var runButton = document.getElementById("runButton");
  var interCount = eCount.value.replace(/\D/g,'');
  var names = ['George','Ringo','Paul','John'];
  
  eCount.addEventListener("input", function(){   
   var value = parseInt(this.value.replace(/\D/g,'')).toLocaleString();
   this.value = value.toLocaleString();
  });


  function runLoop(){   
   interCount = eCount.value.replace(/\D/g,'');
   waitImg(true);
   setTimeout(function(){
    var cachedTime = loopTest("cached");
    var inlineTime = loopTest("inline");
    document.getElementById( "cached" ).innerText = cachedTime+" Cached";
    document.getElementById( "inline" ).innerText = inlineTime+" Not Cached";
    waitImg(false);
   }, 100); // delay to allow update of DOM with waitimg gif
   
  }
  
  function loopTest(meth){
   var worthlessVariable = 0;
   var t0 = performance.now();   
   if( meth == "cached" ){
    for( var i = 0, len = busyCalulations(); i < len; i++) {
     worthlessVariable = i;
    }
   }else{
    for( var i = 0; i < busyCalulations(); i++) {
     worthlessVariable = i;
    }
   }
   var t1 = performance.now();
   return (t1 - t0);
  }

  
  function busyCalulations(){
   // garbage math to simulate doing something
   // it returns interCount after some pointless math
   var limit = Math.floor(Math.random() * 20) + 20;
   return interCount*(limit*names.length)/(limit*names.length);
  }
  
  
  function waitImg(txt){ // display wait timer
   if (txt === true){
    waitDiv.style.visibility = "visible";
    runButton.style.visibility = "hidden";
   }else{
    waitDiv.style.visibility = "hidden";
    runButton.style.visibility = "visible";
   }
  }
 <h1>Loop Tester</h1>
<form onSubmit="return false;">
 Loop Length <input id="loopCount" type="text" value="100,000"><br>
 <br><br>
 <button id="runButton" onClick="runLoop();">Run Test</button>
 <div id="waitDiv" style="visibility: hidden"><img src="https://i.stack.imgur.com/5qXc3.gif"></div>
 <br><br>
 </form>
 <div><p>Times are in milliseconds</p>
  <div id="cached"></div>
  <div id="inline"></div>
 </div>
Sterner
  • 96
  • 4
1

Please refer to this post, that talks about optimising JS loops.

So a simple solution for you problem would be:

let arr = ["a", "b", "c", "d", "e"]
let i = arr.length
while(i--) {
   callFn();
}

The above code would run faster as compared to other conventional looping techniques like

for(let i = 0; i < arr.length; i++) {}

Because the above code would have to fetch arr.length on every iteration.

Akash Babu
  • 950
  • 6
  • 10