AS3 has three kinds of loops—for, for-in, and for-each—but which is fastest? I attempted to answer that question about three years ago, but the article is in dire need of a followup as many version of Flash Player have been released since then and the question is core to our everyday lives as AS3 programmers. So which type of loop is fastest in 2012?

I’ve updated the original test to output better results, but the core of it (even the number of iterations per loop type) hasn’t changed at all.

package
{
	import flash.display.*;
	import flash.utils.*;
	import flash.text.*;
 
	public class LoopSpeed extends Sprite
	{
		private var __logger:TextField = new TextField();
		private function row(cols:Array): void
		{
			__logger.appendText(cols.join(",")+"\n");
		}
 
		public function LoopSpeed()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
			const SIZE:uint = 6000;
			const REPS:uint = 2000;
			var i:int;
			var s:String;
			var a:int;
			var rep:int;
			var before:uint;
			var after:uint;
			var cols:Array;
 
			// Containers to loop over
			var arr:Array = new Array(SIZE);
			var vecFixed:Vector.<int> = new Vector.<int>(SIZE, true);
			var vecVariable:Vector.<int> = new Vector.<int>(SIZE, false);
			var bmdAlpha:BitmapData = new BitmapData(SIZE, 1, true);
			var bmdNoAlpha:BitmapData = new BitmapData(SIZE, 1, false);
			var obj:Object = {};
			var dictStrong:Dictionary = new Dictionary(false);
			var dictWeak:Dictionary = new Dictionary(true);
			var ba:ByteArray = new ByteArray();
 
			// Initialize all containers
			for (i = 0; i < SIZE; ++i)
			{
				arr[i] = int(Math.random()*1000);
			}
			for (i = 0; i < SIZE; ++i)
			{
				vecFixed[i] = arr[i];
			}
			for (i = 0; i < SIZE; ++i)
			{
				vecVariable[i] = arr[i];
			}
			for (i = 0; i < SIZE; ++i)
			{
				bmdAlpha.setPixel32(i, 0, arr[i]);
			}
			for (i = 0; i < SIZE; ++i)
			{
				bmdNoAlpha.setPixel32(i, 0, arr[i]);
			}
			for (i = 0; i < SIZE; ++i)
			{
				obj[i] = i;
			}
			for (i = 0; i < SIZE; ++i)
			{
				dictStrong[i] = i;
			}
			for (i = 0; i < SIZE; ++i)
			{
				dictWeak[i] = i;
			}
			ba.length = SIZE;
			for (i = 0; i < SIZE; ++i)
			{
				ba[i] = i;
			}
 
			row([
				"Loop Type",
				"Array",
				"Fixed Vector",
				"Variable Vector",
				"BMD w/ alpha getPixel32",
				"BMD w/o alpha getPixel32",
				"BMD w/ alpha getPixel",
				"BMD w/o alpha getPixel",
				"Object",
				"Dictionary (strong keys)",
				"Dictionary (weak keys)",
				"ByteArray"
			]);
 
			cols = ["For-Each"];
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for each (i in arr)
				{
					a = i;
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for each (i in vecFixed)
				{
					a = i;
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for each (i in vecVariable)
				{
					a = i;
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			cols.push("n/a", "n/a", "n/a", "n/a"); // no BitmapData tests possible
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for each (i in obj)
				{
					a = i;
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for each (i in dictStrong)
				{
					a = i;
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for each (i in dictWeak)
				{
					a = i;
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			cols.push("n/a"); // no ByteArray test possible
 
			row(cols);
 
			cols = ["For-In"];
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (s in arr)
				{
					a = arr[s];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (s in vecFixed)
				{
					a = vecFixed[s];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (s in vecVariable)
				{
					a = vecVariable[s];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			cols.push("n/a", "n/a", "n/a", "n/a"); // no BitmapData tests possible
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (s in obj)
				{
					a = obj[s];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (s in dictStrong)
				{
					a = dictStrong[s];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (s in dictWeak)
				{
					a = dictWeak[s];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			cols.push("n/a"); // no ByteArray test possible
 
			row(cols);
 
			cols = ["For"];
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (i = 0; i < SIZE; ++i)
				{
					a = arr[i];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (i = 0; i < SIZE; ++i)
				{
					a = vecFixed[i];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (i = 0; i < SIZE; ++i)
				{
					a = vecVariable[i];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (i = 0; i < SIZE; ++i)
				{
					a = bmdAlpha.getPixel32(i, 0);
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (i = 0; i < SIZE; ++i)
				{
					a = bmdNoAlpha.getPixel32(i, 0);
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (i = 0; i < SIZE; ++i)
				{
					a = bmdAlpha.getPixel(i, 0);
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (i = 0; i < SIZE; ++i)
				{
					a = bmdNoAlpha.getPixel(i, 0);
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (i = 0; i < SIZE; ++i)
				{
					a = obj[i];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (i = 0; i < SIZE; ++i)
				{
					a = dictStrong[i];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (i = 0; i < SIZE; ++i)
				{
					a = dictWeak[i];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			before = getTimer();
			for (rep = 0; rep < REPS; ++rep)
			{
				for (i = 0; i < SIZE; ++i)
				{
					a = ba[i];
				}
			}
			after = getTimer();
			cols.push(after-before);
 
			row(cols);
		}
	}
}

I ran this test app in the following environment:

  • Flex SDK (MXMLC) 4.6.0.23201, compiling in release mode (no debugging or verbose stack traces)
  • Release version of Flash Player 11.4.402.265
  • 2.3 Ghz Intel Core i7
  • Mac OS X 10.8.2

And here are the results I got:

Loop Type For-Each For-In For
Array 244 4552 77
Fixed Vector 287 4629 31
Variable Vector 288 4616 31
BMD w/ alpha getPixel32 n/a n/a 145
BMD w/o alpha getPixel32 n/a n/a 122
BMD w/ alpha getPixel n/a n/a 164
BMD w/o alpha getPixel n/a n/a 125
Object 688 4912 366
Dictionary (strong keys) 733 5124 367
Dictionary (weak keys) 816 5197 375
ByteArray n/a n/a 137

Performance Chart

The formatting has certainly gotten better since 2009. We can now see clearly that there is a defined order of speed. The for-in loop is the slowest, for-each faster than that, and in all cases for is the fastest of all. It’s also the only one to provide access to BitmapData and ByteArray collections, which makes it the most flexible. It’s some extra typing and that may interfere with readability, but when speed counts you should opt for good old for.

Spot a bug? Have a suggestion? Post a comment!