AS3 gives you a good number of potential ways you can loop over collections. When Flash Player 10 first came out, I went ahead and tested out the new Vector class in a variety of ways. One of them was to pit it against the collections available in Flash Player 9: Array, Object, Dictionary, ByteArray, and even BitmapData. Below I’ll show you my test and discuss its results.
First, have a look at the test app:
package { import flash.display.*; import flash.utils.*; import flash.text.*; public class MyApp extends Sprite { public function MyApp() { const SIZE:uint = 6000; const REPS:uint = 2000; var i:int; var s:String; var a:int; var b:String; var rep:int; var res:String = "For-each times:\n"; var before:uint; var after:uint; var arr:Array = new Array(SIZE); for (i = 0; i < SIZE; ++i) { arr[i] = int(Math.random()*1000); } var vecFixed:Vector.<int> = new Vector.<int>(SIZE, true); for (i = 0; i < SIZE; ++i) { vecFixed[i] = arr[i]; } var vecVariable:Vector.<int> = new Vector.<int>(SIZE, false); for (i = 0; i < SIZE; ++i) { vecVariable[i] = arr[i]; } var bmdAlpha:BitmapData = new BitmapData(SIZE, 1, true); for (i = 0; i < SIZE; ++i) { bmdAlpha.setPixel32(i, 0, arr[i]); } var bmdNoAlpha:BitmapData = new BitmapData(SIZE, 1, false); for (i = 0; i < SIZE; ++i) { bmdNoAlpha.setPixel32(i, 0, arr[i]); } var obj:Object = {}; for (i = 0; i < SIZE; ++i) { obj[i] = i; } var dictStrong:Dictionary = new Dictionary(); for (i = 0; i < SIZE; ++i) { dictStrong[i] = i; } var dictWeak:Dictionary = new Dictionary(true); for (i = 0; i < SIZE; ++i) { dictWeak[i] = i; } var ba:ByteArray = new ByteArray(); ba.length = SIZE; for (i = 0; i < SIZE; ++i) { ba[i] = i; } before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for each (i in arr) { a = i; } } after = getTimer(); res += "\tArray: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for each (i in vecFixed) { a = i; } } after = getTimer(); res += "\tFixed vec: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for each (i in vecVariable) { a = i; } } after = getTimer(); res += "\tVariable vec: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for each (i in obj) { a = i; } } after = getTimer(); res += "\tObject: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for each (i in dictStrong) { a = i; } } after = getTimer(); res += "\tDictionary (strong keys): " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for each (i in dictWeak) { a = i; } } after = getTimer(); res += "\tDictionary (weak keys): " + (after-before) + "\n"; res += "For-in times:\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (s in arr) { a = arr[s]; } } after = getTimer(); res += "\tArray: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (s in vecFixed) { a = vecFixed[s]; } } after = getTimer(); res += "\tFixed vec: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (s in vecVariable) { a = vecVariable[s]; } } after = getTimer(); res += "\tVariable vec: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (s in obj) { a = obj[s]; } } after = getTimer(); res += "\tObject: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (s in dictStrong) { a = dictStrong[s]; } } after = getTimer(); res += "\tDictionary (strong keys): " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (s in dictWeak) { a = dictWeak[s]; } } after = getTimer(); res += "\tDictionary (weak keys): " + (after-before) + "\n"; res += "For times:\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (i = 0; i < SIZE; ++i) { a = arr[i]; } } after = getTimer(); res += "\tArray: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (i = 0; i < SIZE; ++i) { a = vecFixed[i]; } } after = getTimer(); res += "\tFixed vec: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (i = 0; i < SIZE; ++i) { a = vecVariable[i]; } } after = getTimer(); res += "\tVariable vec: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (i = 0; i < SIZE; ++i) { a = bmdAlpha.getPixel32(i, 0); } } after = getTimer(); res += "\tBMD w/ alpha getPixel32: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (i = 0; i < SIZE; ++i) { a = bmdNoAlpha.getPixel32(i, 0); } } after = getTimer(); res += "\tBMD w/o alpha getPixel32: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (i = 0; i < SIZE; ++i) { a = bmdAlpha.getPixel(i, 0); } } after = getTimer(); res += "\tBMD w/ alpha getPixel: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (i = 0; i < SIZE; ++i) { a = bmdNoAlpha.getPixel(i, 0); } } after = getTimer(); res += "\tBMD w/o alpha getPixel: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (i = 0; i < SIZE; ++i) { a = obj[i]; } } after = getTimer(); res += "\tObject: " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (i = 0; i < SIZE; ++i) { a = dictStrong[i]; } } after = getTimer(); res += "\tDictionary (strong keys): " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (i = 0; i < SIZE; ++i) { a = dictWeak[i]; } } after = getTimer(); res += "\tDictionary (weak keys): " + (after-before) + "\n"; before = getTimer(); for (rep = 0; rep < REPS; ++rep) { for (i = 0; i < SIZE; ++i) { a = ba[i]; } } after = getTimer(); res += "\tByteArray: " + (after-before) + "\n"; var tf:TextField = new TextField(); tf.autoSize = TextFieldAutoSize.LEFT; tf.text = res; addChild(tf); } } }
And the results on a 2.2 Ghz Intel Core 2 Duo with 2 GB of RAM on Mac OS X 10.6:
For-each times:
Array: 377
Fixed vec: 455
Variable vec: 448
Object: 721
Dictionary (strong keys): 652
Dictionary (weak keys): 666
For-in times:
Array: 8730
Fixed vec: 8983
Variable vec: 8940
Object: 8805
Dictionary (strong keys): 8816
Dictionary (weak keys): 8884
For times:
Array: 203
Fixed vec: 105
Variable vec: 104
BMD w/ alpha getPixel32: 290
BMD w/o alpha getPixel32: 222
BMD w/ alpha getPixel: 300
BMD w/o alpha getPixel: 228
Object: 592
Dictionary (strong keys): 487
Dictionary (weak keys): 487
ByteArray: 244Wow, what a range! Depending on your choice of collection and looping strategy, you could see results as low as 104 milliseconds or as high as 8940 milliseconds! That’s over 85x slower!
First things first, for-in loops are just plain slow. Sure, there is conversion to a String if the collection is not keyed by Strings. This is true for Dictionaries where you can use any object for keys as well as Arrays and Vectors which use integers as keys. However it doesn’t seem to make much difference as all collections perform just about equally badly. Bottom line here: stay away from for-in loops unless you really need the keys of an Object or Dictionary.
Once we get away from the awful slowness of for-in loops things get a lot closer together. There is still quite a range though! Consider the for-each loops where the range is from 377 milliseconds to 721 milliseconds. It is about 1.91x faster to iterate over an Array with a for-each loop than it is to iterate over an Object. Dictionaries are also on the slow side and Vectors are on the fast side. It’s quite disappointing and unexpected to see Vectors not be the fastest here. But what if we turn to the lowly and plain for loop?
Here we find our performance champion: Vectors iterated over by a plain old for loop. They are twice as fast as iterating over an Array with a plain old for loop, which is the runner up for speed. This is clearly what Adobe had in mind when they introduced Vectors for increased speed. The Objects and Dictionaries also don’t fare so badly; this is the fastest loop to iterate over them with as well. Likewise, for loops are the only way to iterate over a ByteArray, but doing so is slower than a regular Array so there isn’t much point unless you already have a ByteArray for other reasons.
The for loop is the only way to iterate over the oddball collection here: BitmapData. BitmapData is supposed to hold the pixels of a raster image, but you could choose to use it to store any 24- or 32-bit integers you wanted to in some limited capacity, such as image dimensions. You also have to deal with pre-multiplying and the second (Y) dimension of storage if you want to hold more elements in storage than Flash allows for the width of a BitmapData. But this is all mostly moot as it seems that BitmapData can’t seem to beat out Arrays and Vectors for the speed crown, although it does come close. It’s also the only way to iterate over a ByteArray, but that’s comparably slow.
Here are the above results in a tabular format. I’ve also bolded the fastest choice for each type of loop.
| Collection | For-Each | For-In | For |
|---|---|---|---|
| Array | 377 | 8730 | 203 |
| Fixed Vector | 455 | 8983 | 105 |
| Variable Vector | 448 | 8940 | 104 |
| BitmapData w/ alpha getPixel32 | n/a | n/a | 290 |
| BitmapData w/o alpha getPixel32 | n/a | n/a | 222 |
| BitmapData w/ alpha getPixel | n/a | n/a | 300 |
| BitmapData w/o alpha getPixel | n/a | n/a | 228 |
| Object | 721 | 8805 | 592 |
| Dictionary (strong keys) | 652 | 8816 | 487 |
| Dictionary (weak keys) | 666 | 8884 | 487 |
| ByteArray | n/a | n/a | 244 |
In conclusion, if you want speed you want a for loop.
#1 by Jonnie on October 7th, 2009 · | Quote
I bet after all that work, you’re ready to add while and do while loops to the mix. ;)
#2 by jackson on October 7th, 2009 · | Quote
I sure hope those are equally fast as regular for loops. Knowing Flash, I should probably do the test anyhow…
#3 by Jonnie on October 8th, 2009 · | Quote
I do remember the speeds varying on a case-by-case basis, so you might want to.
#4 by jackson on October 8th, 2009 · | Quote
I tried out the while and do-while loops, but don’t see anything statistically different from for loops. Here are my numbers on an 3.0 Ghz Intel Core 2 Duo with 2GB of RAM on Windows XP:
For-each times: Array: 287 Fixed vec: 242 Variable vec: 242 Object: 346 Dictionary (strong keys): 314 Dictionary (weak keys): 317 For-in times: Array: 5635 Fixed vec: 5566 Variable vec: 5537 Object: 5283 Dictionary (strong keys): 5310 Dictionary (weak keys): 5308 For times: Array: 198 Fixed vec: 67 Variable vec: 64 BMD w/ alpha getPixel32: 145 BMD w/o alpha getPixel32: 133 BMD w/ alpha getPixel: 166 BMD w/o alpha getPixel: 161 Object: 273 Dictionary (strong keys): 229 Dictionary (weak keys): 227 ByteArray: 132 While times: Array: 198 Fixed vec: 67 Variable vec: 63 BMD w/ alpha getPixel32: 145 BMD w/o alpha getPixel32: 134 BMD w/ alpha getPixel: 179 BMD w/o alpha getPixel: 183 Object: 273 Dictionary (strong keys): 228 Dictionary (weak keys): 227 ByteArray: 132 Do-While times: Array: 175 Fixed vec: 73 Variable vec: 56 BMD w/ alpha getPixel32: 141 BMD w/o alpha getPixel32: 128 BMD w/ alpha getPixel: 146 BMD w/o alpha getPixel: 133 Object: 267 Dictionary (strong keys): 225 Dictionary (weak keys): 228 ByteArray: 129#5 by Li on October 8th, 2009 · | Quote
Excellent test. I’ve seen similar ones but this is so clear and effective. Thanks!