Three Interesting Discoveries
Today’s article is about three totally unrelated discoveries I’ve recently made in AS3. These answer three questions I’ve recently had. Should you cache the object you’re looping over with a for-each
loop as a local variable? Can you clear a Dictionary
or Object
with a for-in
loop? Is it faster to write your own version of Vector.indexOf
? All of these questions are answered in today’s article!
First, should you cache the object you’re looping over with a for-each
loop as a local variable? Here’s the suspect code:
for each (var val:SomeType in some.very.long.nested.deep.expression) { }
Often, some.very.long.nested.deep.expression
may be very expensive to retrieve. For example, some of those innocuous-looking fields may in fact be getter functions (function get x(): int
) and therefore very costly to call if the expression is evaluated every iteration of the loop. That’s the key: is it evaluated every iteration of the loop? Here’s some code to test it out:
package { import flash.display.*; import flash.text.*; import flash.utils.*; public class ForEachTest extends Sprite { private var __logger:TextField = new TextField(); private function row(...cols): void { __logger.appendText(cols.join(",")+"\n"); } public function ForEachTest() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); var beforeTime:int; var afterTime:int; var i:int; var REPS:int = 10; var SIZE:int = 10000000; var val:int; var vec:Vector.<int> = new Vector.<int>(SIZE); for (i = 0; i < SIZE; ++i) { vec[i] = i; } var obj:Object = { the: { vector: { val: vec } } }; var count:int; count = 0; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { for each (val in vec) { count += val; } } afterTime = getTimer(); row("Direct,", (afterTime-beforeTime)); count = 0; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { for each (val in obj.the.vector.val) { count += val; } } afterTime = getTimer(); row("Indirect,", (afterTime-beforeTime)); } } }
I ran this test 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.5.31.5
- 2.3 Ghz Intel Core i7
- Mac OS X 10.8.2
And here are the results I got:
Method | Time |
---|---|
Direct | 2428 |
Indirect | 2428 |
At 100 million iterations, I’d say it’s pretty clear that there’s no need to worry about having a bit expression at the end of your for-each
loop.
Next, can you clear a Dictionary
or Object
with a for-in
loop? Here’s a test app:
package { import flash.display.*; import flash.text.*; import flash.utils.*; public class DeleteDictionaryTest extends Sprite { private var __logger:TextField = new TextField(); private function row(...cols): void { __logger.appendText(cols.join(",")+"\n"); } public function DeleteDictionaryTest() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); var obj:Object = { first:"Jackson", last:"Dunstan" }; var key:String; for (key in obj) { delete obj[key]; } row("begin obj properties..."); for (key in obj) { row("\tobj[" + key + "] = " + obj[key]); } row("...end obj properties"); } }
And here’s what it prints:
begin obj properties... ...end obj properties
So yes, you definitely can delete the keys of a Dictionary
while you loop over it with a for-in
loop.
Finally, is it faster to write your own version of Vector.indexOf
? Let’s look at the test app:
package { import flash.display.*; import flash.text.*; import flash.utils.*; public class Playground extends Sprite { private var __logger:TextField = new TextField(); private function row(...cols): void { __logger.appendText(cols.join(",")+"\n"); } public function Playground() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); var beforeTime:int; var afterTime:int; var i:int; var SIZE:int = 10000000; var vec:Vector.<int> = new Vector.<int>(SIZE); for (i = 0; i < SIZE; ++i) { vec[i] = i; } var search:int; var index:int; search = 0; index = -1; beforeTime = getTimer(); for (i = 0; i < SIZE; ++i) { if (vec[i] == search) { index = i; break; } } afterTime = getTimer(); row("Manual (first),", (afterTime-beforeTime)); search = SIZE/2; index = -1; beforeTime = getTimer(); for (i = 0; i < SIZE; ++i) { if (vec[i] == search) { index = i; break; } } afterTime = getTimer(); row("Manual (half-way),", (afterTime-beforeTime)); search = SIZE; index = -1; beforeTime = getTimer(); for (i = 0; i < SIZE; ++i) { if (vec[i] == search) { index = i; break; } } afterTime = getTimer(); row("Manual (end),", (afterTime-beforeTime)); search = 0; index = vec.indexOf(search); afterTime = getTimer(); row("indexOf (first),", (afterTime-beforeTime)); search = SIZE/2; index = vec.indexOf(search); afterTime = getTimer(); row("indexOf (half-way),", (afterTime-beforeTime)); search = SIZE; index = vec.indexOf(search); afterTime = getTimer(); row("indexOf (end),", (afterTime-beforeTime)); } } }
I ran this in the same environment as above and got these results:
Method | Search for first | Search for middle | Search for last |
---|---|---|---|
Manual | 0 | 13 | 31 |
Vector.indexOf |
0 | 13 | 24 |
These results are on a 10 million long Vector
and are basically the same all the way up to when the element you’re looking for is at 5 million. Then, at least on my test computer, the Vector.indexOf
function starts to outpace the for
loop to the point that the for
loop is about 30% slower at the 10 millionth element. So, if you have a very long Vector
you should use indexOf
. Otherwise, it’s a bit of a toss-up. You might see some performance gains on a lot of searches that don’t go very far into the Vector
because you’re eliminating the function call overhead of indexOf
.
Spot a bug? Have a discovery to share or a question to ask? Post a comment!
#1 by Gil Amran on December 17th, 2012 ·
Cool, I love your posts!
You should start thinking about writing a BIG app that tests all these things that you test…
I know that Adobe is working on the new AS3! it will be great if you could do some tests between AS3 and the new AS3…
You probably will do, so you better start collecting it all into one BIG “Test the Language” app…
Keep up the great work
Gil Amran
XTDStudios
#2 by orion elenzil on December 17th, 2012 ·
interesting. i wonder how indexOf compares for other data types.
#3 by RTLShadow on December 19th, 2012 ·
Very interesting, thanks!
#4 by skyboy on January 1st, 2013 ·
Vector.indexOf
uses === rather than == in its comparisons (== has a slight overhead for checking/casting mismatched types): it’s most noticeable if you have a * typed vector that contains doubles, ints and Strings when looking for an int. That change may even level indexOf/manual since === performs fewer checks than == before comparing the values.#5 by jiangbo on February 2nd, 2013 ·
yeah,belive the protist api is right indexOf is faster than for loop when find
#6 by Ben on February 3rd, 2014 ·
I retested the indexOf optimization and found out that the manual version seems to be faster and it doesn’t really matter if I use === or ==. Maybe there was a Flash Player update that optimized this or it’s something on my system.
Tested on a i7-3770 3.4 GHz compiled with AIR 3.9 (ASC 2).
With ===:
Manual (first),,0
Manual (half-way),,9
Manual (end),,20
indexOf (first),,21
indexOf (half-way),,30
indexOf (end),,49
With ==:
Manual (first),,0
Manual (half-way),,9
Manual (end),,21
indexOf (first),,21
indexOf (half-way),,30
indexOf (end),,50
Out of curiosity I increased the size to 100,000,000 and the results seem to confirm my other results (with a size of 10,000,000):
Manual (first),,0
Manual (half-way),,86
Manual (end),,261
indexOf (first),,261
indexOf (half-way),,355
indexOf (end),,542
#7 by jackson on February 3rd, 2014 ·
That’s an interesting find. Perhaps one of the five Flash Player versions since the article was written introduced an optimization. Or, more likely the new ASC 2.0 compiler. It’d be interesting to do a bytecode comparison of the “manual” loop between ASC 1.0 and ASC 2.0 to isolate that variable.
#8 by Chuck B on March 20th, 2014 ·
The last 3 indexOf tests are missing beforeTime = getTimer() and therefore more time elapsed is being reported for those tests than was actually taken.
#9 by jackson on March 20th, 2014 ·
15 months and you’re the first one to catch this. Thanks for pointing it out! I’ll see if I can post a followup article correcting the mistake.