72

In his book Even Faster Web Sites Steve Sounders writes that a simple way to improve the performance of a loop is to decrement the iterator toward 0 rather than incrementing toward the total length (actually the chapter was written by Nicholas C. Zakas). This change can result in savings of up to 50% off the original execution time, depending on the complexity of each iteration. For example:

var values = [1,2,3,4,5];
var length = values.length;

for (var i=length; i--;) {
   process(values[i]);
}

This is nearly identical for the for loop, the do-while loop, and the while loop.

I'm wondering, what's the reason for this? Why is to decrement the iterator so much faster? (I'm interested in the technical background of this and not in benchmarks proving this claim.)


EDIT: At first sight the loop syntax used here looks wrong. There is no length-1 or i>=0, so let's clarify (I was confused too).

Here is the general for loop syntax:

for ([initial-expression]; [condition]; [final-expression])
   statement
  • initial-expression - var i=length

    This variable declaration is evaluated first.

  • condition - i--

    This expression is evaluated before each loop iteration. It will decrement the variable before the first pass through the loop. If this expression evaluates to false the loop ends. In JavaScript is 0 == false so if i finally equals 0 it is interpreted as false and the loop ends.

  • final-expression

    This expression is evaluated at the end of each loop iteration (before the next evaluation of condition). It's not needed here and is empty. All three expressions are optional in a for loop.

The for loop syntax is not part of the question, but because it's a little bit uncommon I think it's interesting to clarify it. And maybe one reason it's faster is, because it uses less expressions (the 0 == false "trick").

9
  • 1
    aren't you missing a termination condition? Commented Aug 19, 2010 at 10:17
  • 9
    The termination condition is i--. It'll be 0 (false) when i is 0 before decrementing. Since the condition has the side effect of decrementing the variable itself, there's no need for the third (change/increment/whatever) expression in the statement. Commented Aug 19, 2010 at 10:26
  • @Gumbo - Why do I have to start with length-1? You can replace process with alert to check. @Dave - The terminal condition evaluates to true if the i equals 0 Commented Aug 19, 2010 at 10:28
  • 1
    @Soundlink: Actually, it evaluates to false if i is 0. It evaluates true the rest of the time. Commented Aug 19, 2010 at 10:29
  • 1
    @Gumbo: The first time through the loop checks the condition -- read: it evaluates i--. Commented Aug 19, 2010 at 10:31

13 Answers 13

69

I'm not sure about Javascript, and under modern compilers it probably doesn't matter, but in the "olden days" this code:

for (i = 0; i < n; i++){
  .. body..
}

would generate

move register, 0
L1:
compare register, n
jump-if-greater-or-equal L2
-- body ..
increment register
jump L1
L2:

while the backward-counting code

for (i = n; --i>=0;){
  .. body ..
}

would generate

move register, n
L1:
decrement-and-jump-if-negative register, L2
.. body ..
jump L1
L2:

so inside the loop it's only doing two extra instructions instead of four.

Sign up to request clarification or add additional context in comments.

3 Comments

Worth noting that "in the olden days" javascript would never have been converted into machine code, so it's a little bit of a moot point.
@skeggse JavaScript still has an interpreter i.e. the browser, that has to decide how to execute the code. While it doesn't exactly get "compiled", it has to be sent to the processor some how. What should have been said was interpreter instead of compiler, but to say it doesn't get turned into machine code cannot be completely accurate. Although that's to the browser's discretion. Mozilla uses Spider Monkey for example
@haelmic pretty sure we're saying the same thing. Original implementations of javascript consisted of an interpreter, whereas modern versions such as Spider Monkey and V8 selectively JIT-compile code.
30

I believe the reason is because you're comparing the loop end point against 0, which is faster than comparing against < length (or another JS variable).

It is because the ordinal operators <, <=, >, >= are polymorphic, so these operators require type checks on both the left and right sides of the operator to determine what comparison behaviour should be used.

There are some very good benchmarks available here:

What's the Fastest Way to Code a Loop in JavaScript

2 Comments

Very interesting link, it's a pity he didn't add the test with for loop using prefix ++i operator, according to prototypejs.org/api/array should be faster rather than using postfix i++ increment operator.
Any difference between pre- (++i) and post- (i++) is dependent on the browser's implementation of Javascript (or is vanishingly small). The logic difference is far more important (Douglas Crockford has pointed out it's awfully easy to get the logic wrong with this construct.) The pre- (++i) version increments/decrements before the rest of the statement, while the post- (i++) version increments/decrements after. i-- works as the order is (1]exit if 0, 2]decrement, 3]perform loop); --i would fail as the order (1]decrement, 2]exit if 0, 3]perform loop) would never handle the 0th element.
16

It is easy to say that an iteration can have less instructions. Let’s just compare these two:

for (var i=0; i<length; i++) {
}

for (var i=length; i--;) {
}

When you count each variable access and each operator as one instruction, the former for loop uses 5 instructions (read i, read length, evaluate i<length, test (i<length) == true, increment i) while the latter uses just 3 instructions (read i, test i == true, decrement i). That is a ratio of 5:3.

2 Comments

Wouldn't you want to set i = length, since the for loop does the conditional test for the first iteration as well?
You missed "Read i" in counting number of instructions in latter for loop which makes the ratio 5:3.
7

What about using a reverse while loop then:

var values = [1,2,3,4,5]; 
var i = values.length; 

/* i is 1st evaluated and then decremented, when i is 1 the code inside the loop 
   is then processed for the last time with i = 0. */
while(i--)
{
   //1st time in here i is (length - 1) so it's ok!
   process(values[i]);
}

IMO this one at least is a more readble code than for(i=length; i--;)

2 Comments

The question is not to find a more readable syntax, but to find an explanation why to decrement the iterator is faster.
You have the best answer, while(i--) is the fastest as per : jsperf.com/while-vs-for
4

for increment vs. decrement in 2017

In modern JS engines incrementing in for loops is generally faster than decrementing (based on personal Benchmark.js tests), also more conventional:

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

It depends on the platform and array length if length = array.length has any considerable positive effect, but usually it doesn't:

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

Recent V8 versions (Chrome, Node) have optimizations for array.length, so length = array.length can be efficiently omitted there in any case.

1 Comment

increment is still faster than decrement in 2019 jsperf.com/ppi-vs-ipp-forloop/9
3

There is an even more "performant" version of this. Since each argument is optional in for loops you can skip even the first one.

var array = [...];
var i = array.length;

for(;i--;) {
    do_teh_magic();
}

With this you skip even the check on the [initial-expression]. So you end up with just one operation left.

3 Comments

Hey Andreas, FYI this is totally equivalent to the for loop explained by @Gumbo. You just took the initialization part outside for loop which is anyways taking same time. Moreover, now you should better convert for to while.
also if some operation modifies the counter and you 'skip' over the zero, you got an infinite loop. granted, an unlikely scenario, but it's still a good idea to do something like for(;i-->=0;)
Just use while, which is even faster
3

I've been exploring loop speed as well, and was interested to find this tidbit about decrementing being faster than incrementing. However, I have yet to find a test that demonstrates this. There are lots of loop benchmarks on jsperf. Here is one that tests decrementing:

http://jsperf.com/array-length-vs-cached/6

Caching your array length, however (also recommended Steve Souders' book) does seem to be a winning optimization.

1 Comment

Actually, it does seem to be faster in Safari -- so frustrating that every browser is different!
3

in modern JS engines, the difference between forward and reverse loops is almost non-existent anymore. But the performance difference comes down to 2 things:

a) extra lookup every of length property every cycle

//example:
    for(var i = 0; src.length > i; i++)
//vs
    for(var i = 0, len = src.length; len > i; i++)

this is the biggest performance gain of a reverse loop, and can obviously be applied to forward loops.

b) extra variable assignment.

the smaller gain of a reverse loop is that it only requires one variable assignment instead of 2

//example:
    var i = src.length; while(i--)

Comments

1

I've conducted a benchmark on C# and C++ (similar syntax). There, actually, the performance differs essentially in for loops, as compared to do while or while. In C++, performance is greater when incrementing. It may also depend on the compiler.

In Javascript, I reckon, it all depends on the browser (Javascript engine), but this behavior is to be expected. Javascript is optimized for working with DOM. So imagine you loop through a collection of DOM elements you get at each iteration, and you increment a counter, when you have to remove them. You remove the 0 element, then 1 element, but then you skip the one that takes 0's place. When looping backwards that problem disappears. I know that the example given isn't just the right one, but I did encounter situations where I had to delete items from an ever-changing object collection.

Because backward looping is more often inevitable than forward looping, I am guessing that the JS engine is optimized just for that.

Comments

1

Have you timed it yourself? Mr. Sounders might be wrong with regards to modern interpreters. This is precisely the sort of optimization in which a good compiler writer can make a big difference.

2 Comments

No, I have not timed it myself. It would be interesting to know if the current JavaScript engines behave different.
@Soundlink: You presumably have at least one web browser (as evidenced by the fact that you're posting on a web site). Try it out. :)
1

I am not sure if it's faster but one reason i see is that when you iterate over an array of large elements using increment you tend to write:

for(var i = 0; i < array.length; i++) {
 ...
}

You are essentially accessing the length property of the array N (number of elements) times. Whereas when you decrement, you access it only once. That could be a reason.

But you can also write incrementing loop as follows:

for(var i = 0, len = array.length; i < len; i++) {
 ...
}

1 Comment

It's true, that accessing the length property N times is bad, but I don't think that's the reason, because in his book he has already considered that for the incrementing loop.
0

It's not faster (at least in modern browsers):

// Double loops to check the initialization performance too
const repeats = 1e3;
const length = 1e5;

console.time('Forward');
for (let j = 0; j < repeats; j++) {
  for (let i = 0; i < length; i++) {}
}
console.timeEnd('Forward'); // 58ms

console.time('Backward');
for (let j = repeats; j--;) {
  for (let i = length; i--;) {}
}
console.timeEnd('Backward'); // 64ms

The difference is even bigger in case of an array iteration:

const repeats = 1e3;
const array = [...Array(1e5)];

console.time('Forward');
for (let j = 0; j < repeats; j++) {
  for (let i = 0; i < array.length; i++) {}
}
console.timeEnd('Forward'); // 34ms

console.time('Backward');
for (let j = 0; j < repeats; j++) {
  for (let i = array.length; i--;) {}
}
console.timeEnd('Backward'); // 64ms

Comments

-1

My Chrome version: Version 134.0.6998.118 (Official Build) (64-bit).

So i've tested fastest loop method for both Map and Arrays in my Chrome console (lol), and here's what i found:

If you are using a Map, the fastest way to loop over it is to use the traditional looping method, without any special method, not even using size cache (which is called length cache if it's an array) or even a post-increment.

Here is the code that you can try yourself:

let map = new Map();

for (let i = 0; 1e3 > i; ++i) map.set(i, Math.random());

const startTime = performance.now();
for (let i = 0; i < map.size; i++) {
    console.log(map.get(i));
}
const endTime = performance.now();
const result = endTime - startTime;
console.clear();

console.info(`delay: ${result} miliseconds`);

If you are looping over an Array, things get different for some reason. If the length of the Array is in the thousands range or less, it's best to use this code:

let array = [];

for (let i = 0; 1e3 > i; ++i) array.push(Math.random());

const startTime = performance.now();
for (let i = 0, arrayLength = array.length; i < arrayLength; ++i) {
    console.log(array[i]);
}
const endTime = performance.now();
const result = endTime - startTime;
console.clear();

console.info(`delay: ${result} miliseconds`);

However, if the length of the array is in the tens of thousands or more, it's best to use the decrement looping method.

Here is the code that you can try:

let array = [];

for (let i = 0; 1e4 > i; ++i) array.push(Math.random());

const startTime = performance.now();
for (let i = array.length, h = i; --i;) {
    console.log(array[i]);
    if (1 === i) h = 0, console.log(array[h]); else continue;
}
const endTime = performance.now();
const result = endTime - startTime;
console.clear();

console.info(`delay: ${result} miliseconds`);

Keep in mind that different browsers, environment, etc., may result in different results.

2 Comments

Putting integer indices in a Map doesn't really make sense, use an array.
Your code is mostly benchmarking console.log, not the loop.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.