Unrolling your loop for better performance in Javascript
Loop unrolling, is a loop transformation technique that attempts to optimize a program’s execution speed at the expense of its size. – Wikipedia
What this means is that we are trying to limit the number of iterations to mitigate the overhead in a loop.
Consider this.
1 2 3 4 | var i=values.length; while(i--){ processData(values[i]); } |
If we say that there are only 5 items in the array that will make it easier for us to unroll it.
1 2 3 4 5 | processData(values[0]); processData(values[1]); processData(values[2]); processData(values[3]); processData(values[4]); |
It isn’t pretty and it would probably be annoying to maintain such an approach if the size of the array grow or shrinks. The performance gain for this example wont make the downside worth it, but if the array was quite large then it would be useful, but still even more annoying to maintain.
Tom Duff to the rescue
Tom came up with a brilliant solution/pattern for unrolling loops in C, which got the name Duff’s Device and was later on converted to Javascript by Jeff Greenberg.
Here is the javascript version
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var iterations = Math.ceil(values.length / 8); var startAt = values.length % 8; var i = 0; do { switch(startAt){ case 0: process(values[i++]); case 7: process(values[i++]); case 6: process(values[i++]); case 5: process(values[i++]); case 4: process(values[i++]); case 3: process(values[i++]); case 2: process(values[i++]); case 1: process(values[i++]); } startAt = 0; } while (--iterations > 0); |
The idea with this pattern is that each trip through the loop does the work of 1-8 iterations of a normal loop and Tom also figured out that 8 was the optimal number to use. A very important aspect of this pattern is the use of modulus, because not all arrays will be divisible by 8.
This technique might look a bit strange but it will actually run faster then a normal loop. How ever, we can make it run even faster, if we remove the switch statement from the loop, because conditionals have a performance overhead.
This example was introduced in Speed Up Your Site by Andrew B. King.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var iterations = Math.floor(values.length / 8); var leftover = values.length % 8; var i = 0; if (leftover > 0){ do { process(values[i++]); } while (--leftover > 0); } do { process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); } while (--iterations > 0); |
This optimized Duff’s Device is 39 percent faster than the original and 67 percent faster than a normal for loop. - Speed Up Your Site, Andrew B. King
Finale word
Before you go and change all of your loops, you should consider if you really need to use this pattern. For small arrays the performance gain is to small to have any greater impact, so you should only applied this technique when you find a bottleneck that is related to a loop.
Further Reading
Even Faster Web Sites: Performance Best Practices for Web Developers