Thursday, December 20, 2007

Decrementing vs Incrementing

A while back, I had read some things about how decrementing loops in javascript were faster then incrementing, especially in IE. (see http://www.moddular.org/log/javascript-loops) The main point being that it is faster to compare to 0 then any other number. As a result, I had implemented some decrementing loops in my own code (loop unrolling seemed too heavy handed) but haven't been too compelled to make a habit of it. Today I decided to investigate this again, since I had some lingering doubts about if any of this made a difference at all... So, here it is.

My test code consists of the following:

var limit = 1000000;
function testi(){
var n = 0;
for (var i = 0; i < n =" i;" n =" 0;" i =" limit;"> 0; i--)
n = i;
}

time1 = new Date().valueOf();
testi();
time2 = new Date().valueOf();
document.write("time incrementing (" + limit + " times) = " + (time2 - time1) + "
");

time1 = new Date().valueOf();
testd();
time2 = new Date().valueOf();
document.write("time decrementing (" + limit + " times) = " + (time2 - time1) + "
");


Fairly straight forward. I can change the limit value and check to see what the differences are for different size loops. I compared in Firefox 2 and IE 7. And I found the following.

  1. At a million loops in FF and IE, decrementing is usually twice as fast as incrementing (~220ms vs ~440ms). But how important is a 200ms baseline difference when you are doing a loop a million times? Presumably, whatever you are doing inside of that loop will most likely eclipse the difference.*
  2. If we decrease the loop to 100,000, the difference in IE becomes ~20ms decrementing vs ~40ms incrementing. And Firefox, surprisingly, shows the same results.
  3. At 10,000 loops, Firefox wavers between 0 and 10 ms for either incrementing or decrementing. As does IE.
  4. At 1,000 loops, we are in 0ms land for both IE and FF.
So, we see that at a million, decrementing saves us some time -- especially in IE. When we drop down to 100,000, the differences between the browsers disappear, and while decrementing seems to be twice as fast, the ~20 ms difference is tiny in the grand scheme of things. At 10,000, the differences incrementing and decrementing in both browsers disappears entirely.

One problem with decrementing loops I have found is that they don't behave the same as incrementing loops. If you are looping through an array, you end up going through it backwards. This is sometimes a problem, if for example you are executing a string of functions stored in an array, and you want to ensure that the first one in is executed first. A simple way to fix this, is to find the index by subtracting your decrementing index from your limit. But does putting this simple bit of math into the loop corrodes any performance gains we made by decrementing in the first place? To find out, I substituted the n = i; assignment in the decrementing loop function with a n = limit - i; assignment. I found that:

  1. In Firefox (at 1 million loops), the cost of decrementing rose to ~550ms vs ~440ms for incrementing. Erasing any gains and adding cost. In IE, the time for decrementing increased beyond the time for incrementing.
  2. Below 1 million loops, decrementing lost all of its advantage (at best being equal) when the compensating math was implemented.
In conclusion, it seems that decrementing loops are more efficient, but only when the loops are extremely large. This advantage pales next to whatever else you might be doing in an extremely large loop. Above all, decrementing is counter-intuitive and requires compensating math when used with loops that are order dependent (or might be order dependent), and this compensation nullifies any gains that decrementing gives in the first place. As with many optimization tricks, it seems that the cure is worse then the symptom.

*Strangely, in Internet Explorer only, if I reverse the order of the loops and execute the decrementing function first (at a million loops), decrementing takes ~210ms and incrementing takes anywhere from 900 to over 2000 ms. This is a big difference, and I haven't been able to figure out the source of the disparity.

No comments: