For Vs. While
I’ve recently been seeing more and more usage of while loops by those who I presume are interested in performance. I’ve always assumed that these was not faster than for loops, but today I am finding out.
I was taught in school that for loops are syntax sugar one small step beyond while loops. That is, these are equivalent:
for (a; b; c) { d; } a; while (b) { d; c; }
I learned that while studying C and C++ though and it was thoroughly supported when I learned my first assembly language and loops were indeed not native operations of the CPU. :) But these days I’m dealing with AS3, AS2, and JavaScript and things are much murkier with a VM in the way. So I devised a test to see for myself if for loops are indeed still just syntax sugar:
package { import flash.display.*; import flash.text.*; import flash.utils.*; /** * An app to test the speed of for loops versus while loops * @author Jackson Dunstan */ public class ForVsWhile extends Sprite { public function ForVsWhile() { var logger:TextField = new TextField(); logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); function log(msg:*): void { logger.appendText(msg + "\n"); } var i:int; const NUM_ITERATIONS:int = 1000000000; var beforeTime:int; beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) {} log("For (forward): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = NUM_ITERATIONS; i--; ) {} log("For (backward): " + (getTimer()-beforeTime)); beforeTime = getTimer(); i = 0; while (i < NUM_ITERATIONS) {++i;} log("While (forward): " + (getTimer()-beforeTime)); beforeTime = getTimer(); i = NUM_ITERATIONS; while (i--) {} log("While (backward): " + (getTimer()-beforeTime)); } } }
And the results are:
Environment | For (forward) | For (backward) | While (forward) | While (backward) |
---|---|---|---|---|
3.0 Ghz Intel Core 2 Duo, 4 GB RAM, Windows XP | 2218 | 2365 | 2217 | 2365 |
Then I made a test for JavaScript:
/** * An app to test the speed of for loops versus while loops * @author Jackson Dunstan */ function log(msg) { document.write(msg + "<br/>"); } function getTimer() { return (new Date()).getTime(); } var i; var NUM_ITERATIONS = 1000000; var beforeTime; beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) {} log("For (forward): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = NUM_ITERATIONS; i--; ) {} log("For (backward): " + (getTimer()-beforeTime)); beforeTime = getTimer(); i = 0; while (i < NUM_ITERATIONS) {++i;} log("While (forward): " + (getTimer()-beforeTime)); beforeTime = getTimer(); i = NUM_ITERATIONS; while (i--) {} log("While (backward): " + (getTimer()-beforeTime));
Note the lower number of iterations due to low browser timeout lengths. The results are:
Environment | For (forward) | For (backward) | While (forward) | While (backward) |
---|---|---|---|---|
3.0 Ghz Intel Core 2 Duo, 4 GB RAM, Windows XP, Firefox 3.5 | 3 | 2 | 3 | 2 |
3.0 Ghz Intel Core 2 Duo, 4 GB RAM, Windows XP, IE 8 | 234 | 94 | 234 | 94 |
3.0 Ghz Intel Core 2 Duo, 4 GB RAM, Windows XP, Chrome 3 | 8 | 6 | 8 | 6 |
3.0 Ghz Intel Core 2 Duo, 4 GB RAM, Windows XP, Safari 4 | 3 | 2 | 3 | 2 |
Then I made a test for AS2:
/** * An app to test the speed of for loops versus while loops * @author Jackson Dunstan */ class ForVsWhile { function ForVsWhile() { _root.createTextField("log", 0, 0, 0, 640, 480); var log:Function = function(msg:String): Void { _root.log.text += msg + "\n"; } var i:Number; var NUM_ITERATIONS:Number = 10000000; var beforeTime:Number; beforeTime = getTimer(); for (i = 0; i < NUM_ITERATIONS; ++i) {} log("For (forward): " + (getTimer()-beforeTime)); beforeTime = getTimer(); for (i = NUM_ITERATIONS; i--; ) {} log("For (backward): " + (getTimer()-beforeTime)); beforeTime = getTimer(); i = 0; while (i < NUM_ITERATIONS) {++i;} log("While (forward): " + (getTimer()-beforeTime)); beforeTime = getTimer(); i = NUM_ITERATIONS; while (i--) {} log("While (backward): " + (getTimer()-beforeTime)); } static function main(mc:MovieClip): Void { var app:ForVsWhile = new ForVsWhile(); } }
Again note the lower number of iterations due to the slowness of AS2 and the old VM. The results are:
Environment | For (forward) | For (backward) | While (forward) | While (backward) |
---|---|---|---|---|
3.0 Ghz Intel Core 2 Duo, 4 GB RAM, Windows XP | 1845 | 1832 | 1797 | 1832 |
As you can see from the results tables, there is usually no difference between for or while loops. The lone exception to this is AS2 where forward-iterating while loops actually outperform their for equivalents. I don’t know why this is, but given AS2’s diminishing usefulness in Flash, I don’t mind so much.
It gets more interesting when you consider both forward iteration and backward iteration. Forward iteration is faster in AS3 by a small margin, slower in JavaScript by a factor of more than two for all browsers tested, and mixed by a small margin in AS2 due to the strange forward iteration performance. Unfortunately, this harms code portability in the performance arena. When porting between these languages you may need to revisit any performance-critical areas of code and, if possible, change the directionality of their loops. Even worse, these inconsistent test results make it difficult to remember which direction to loop in various languages for maximum performance. I suppose now you at least have this page as a reference. :)
Enjoy your Thanksgiving and Black Friday festivities!
#1 by Tommy Kjær Andersen on November 27th, 2009 ·
if i remember rigth the for loops are converted to while loops by the compiler, so the results souldt be the same.
#2 by jackson on November 27th, 2009 ·
That’s the meaning of syntax sugar. Repeated testing (above) shows that this is, quite unfortunately, not the case.
#3 by Robert Penner on November 30th, 2009 ·
A few points:
1. Some people say there is a speed difference between pre-increment and post-increment.
2. When I move the “forward while” code to the top, it runs slower than the “forward for” (AS3, Flash CS4). It often seems that code running first has a disadvantage.
3. I decompiled the loops; the for and while loops come back exactly the same (AS2 and AS3).
#4 by jackson on November 30th, 2009 ·
Thanks for the points!
1. Yes, I think they’re wrong. :) If you look at the bytecodes available in AVM2 you’ll see instructions like “increment_i”, “inclocal_i”, “add_i” and so forth. Adobe puts out a good overview. There’s no mention of “pre” and “post”; that is only an order of bytecode generation by the compiler based on the precedence rules of AS3. In the end it is the same bytecode. Still, that’s something I could have been clearer about in the article.
2. My tests didn’t show any difference between “forward while” and “forward for” for AS3. Do you see any difference when you don’t swap the order around? How about when you use Flex instead of CS4? I’m not seeing any difference when I swap any of the tests around.
3. This is good to know! I wonder what accounts for the mysterious slowness “forward for” on AS2…
#5 by skyboy on October 22nd, 2010 ·
I believe I have discovered why the forward loops are faster in AS3: nJIT.
It sees that you’re comparing against a constant, and inlines it, making the effective bytecode:
While the bytecode of the backwards loop is effectively (there may be more to it, I’ve noticed that adding
gt 0
can speed it up):And the forwards loop is slower in because you used
var
instead ofconst
making the equive AS3 bytecode:get/set local are slow operations, if you use a var in AS3 you should also see the slowdown as in JS.
The slowdown/speedup in AS2 however is confusing. I can’t come up with a remotely valid reason for this, or even how to do it on the player’s end.
#6 by jackson on October 22nd, 2010 ·
I’ve looked at the bytecode generated by MXMLC for
var
andconst
before and never have I seen any differences. It seems to me that the only difference is that there is extra checking at compile time to make sure you don’t reassign the variable when it is specified asconst
. That is quite useful, but the optimizations thatconst
brings along don’t seem to be present in MXMLC, at least of version 4.1. For example, there is no constant folding, even in the case of simple integerconst
variables.So in the case of the loops in the article, it would seem to me that the JIT would, if it’s doing anything, see the literal
0
as a constant and not the variableNUM_ITERATIONS
. It seems unlikely that the JIT would see things the other way around and optimize for the variable over the literal.To me, it’s still a mystery why forward loops run faster than backward loops. My assumption is that since forward loops are by far more common, this area of performance—on whatever level—has received more attention from Adobe as it represents a far bigger performance win.
#7 by skyboy on October 23rd, 2010 ·
Though, based on for-loop performance from the two tests I did, it looks like nJIT is doing something. However, from the while loop performance, it looks like my computer started doing another task while testing.
2.703 GHz single core Intel Celeron, 2 GB DDR1 RAM, WinXP Home, 42 running processes (2 instances of Firefox); and the latest stand-alone release player
and the modified test:
#8 by jackson on October 23rd, 2010 ·
Hmm, perhaps
const
has been improved in a recent version of MXMLC. I’ll look into it and post any findings. Thanks for the tip!#9 by Bartosz on October 31st, 2010 ·
The JS findings aren’t entirely accurate. Because you are only performing one operation on the reverse while (i–), you are performing half the work the forward loop is doing.
If you change the loops to use the same syntax,
while (i>0) { i–; } You’ll see the performance is exactly the same.
But yours is a good hack to know if you are just iterating end to end.
#10 by jackson on October 31st, 2010 ·
The JS test is the same as the AS2 and AS3 tests in that all of them use essentially the same loop code. I like to think of the
while (i--)
approach as more of a feature of backwards looping than a hack. Perhaps I’ll do a followup to this article to dig into the bytecode generated by each of these loops. It’s quite likely that you’re right about them: the backwards loop is probably doing less work.#11 by Morg. on August 9th, 2011 ·
In a way, for is syntactic sugar on top of while … but why ?
It looks to me that while($i<$max) is much more straightforward as it explicitly shows that the CPU is going to have to compare those values on each iteration (and nothing else) …
Even though most people seem to believe for should be used in every one of these cases, isn't it much more logical to actually use the while loop, which is clearly more explicit on what it's asking the CPU to do ?
What is your point of view on those aspects when considering performance and precision, including foreach() which hides the $i although it's using it internally anyway ?
#12 by jackson on August 9th, 2011 ·
Since the performance is identical (even in the update article), I’d say it’s a matter of highly-subjective readability preference. Many people find comfort in seeing the loop initialization, check, and update all in one line because usually all three are required and the form acts as a convenient little checklist. If you don’t need one, it’s usually the initialization and then it’s still easy to make a two-part checklist: