Cast Speed
One of the first articles I wrote for this site covered the two types of casts available to the AS3 programmer. In that article I covered the syntax of the two as well as some of the quirks. Today I’ll cover the performance differences between them.
Jonnie Hallman left a comment on the first article claiming that the as
operator (e.g. obj as BitmapData
) is 4.5x faster than the function call style (e.g. BitmapData(obj)
). We’ll see later if he’s right, but for now let’s take a look at a couple of functions demonstrating the two approaches:
private function functionCallStyle(bmd:BitmapData): void { BitmapData(bmd); }
MXMLC 4.1 compiles the above to this bytecode:
function private::functionCallStyle(flash.display::BitmapData):void /* disp_id 0*/ { // local_count=2 max_scope=1 max_stack=2 code_len=9 0 getlocal0 1 pushscope 2 findpropstrict flash.display::BitmapData 4 getlocal1 5 callpropvoid flash.display::BitmapData (1) 8 returnvoid }
This is quite straightforward:
findpropstrict
– Look up theBitmapData
classgetlocal1
– Get thebmd
variablecallpropvoid
– Do the cast
Now let’s look at the as
operator version:
private function asKeyword(bmd:BitmapData): void { bmd as BitmapData; }
And here’s the bytecode generated for this version:
function private::asKeyword(flash.display::BitmapData):void /* disp_id 0*/ { // local_count=2 max_scope=1 max_stack=2 code_len=8 0 getlocal0 1 pushscope 2 getlocal1 3 getlex flash.display::BitmapData 5 astypelate 6 pop 7 returnvoid }
This version involves one more instruction, but it also pretty simple:
getlocal1
– Get the bmd variablegetlex BitmapData
– Find theBitmapData
typeastypelate
– Do theas
castpop
– Discard the resulting value (aBitmapData
ornull
)
It’s not clear which version is faster from just the bytecode, so let’s do a performance test:
package { import flash.display.*; import flash.text.*; import flash.utils.*; public class CastSpeed extends Sprite { public function CastSpeed() { var logger:TextField = new TextField(); logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); function log(msg:*): void { logger.appendText(msg+"\n"); } const ITERATIONS:int = 5000000; var i:int; var beforeTime:int; var afterTime:int; var bmd:BitmapData = new BitmapData(1, 1); var spr:Sprite = new Sprite(); log("Cast succeeds:"); beforeTime = getTimer(); for (i = 0; i < ITERATIONS; ++i) { BitmapData(bmd); } afterTime = getTimer(); log("\tFunction call style: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < ITERATIONS; ++i) { bmd as BitmapData; } afterTime = getTimer(); log("\tAs keyword: " + (afterTime-beforeTime)); log("Cast fails:"); beforeTime = getTimer(); for (i = 0; i < ITERATIONS; ++i) { try { BitmapData(spr); } catch (err:TypeError) { } } afterTime = getTimer(); log("\tFunction call style: " + (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < ITERATIONS; ++i) { spr as BitmapData; } afterTime = getTimer(); log("\tAs keyword: " + (afterTime-beforeTime)); } } }
And here are the results with Flash Player 10.1
Environment | Cast Succeeds | Cast Fails | ||
---|---|---|---|---|
Function Call Style | As Keyword | Function Call Style | As Keyword | |
3.0 Ghz Intel Core 2 Duo, Windows XP | 149 | 38 | 9238 | 39 |
2.0 Ghz Intel Core 2 Duo, Mac OS X | 256 | 57 | 18245 | 58 |
Now it’s clear which version is faster. Even when the cast succeeds, the as
keyword is about 4x-4.5x faster than the function call approach. This is right in line with Jonnie Hallman’s comment on the last article, even with the introduction of Flash Player 10.1. Still, it’s good to know about the slightly better performance on Windows and even better to know about the radically worse performance due to the try/catch
required by the function call version’s error throwing, as Robert Penner pointed out in a comment. That penalty results in a cast that’s 236x-314x slower than the equivalent as
operator approach!
You might want to review any performance critical code to see if you have any function call casting in there, especially if you expect the cast to fail!
#1 by Till Schneidereit on September 27th, 2010 ·
Interesting test, thanks!
One (big) caveat, though: In pretty much all real-world cases, you’d want to actually do something with the result of the cast. just assigning the result to a variable changes things pretty dramatically for me:
is only about 20% faster than
This is in the release version of player 10.1.52.14 on a 2.8 Ghz Intel Core 2 Duo, Mac OS X 10.6.
Also, I think that “as” should only be used if either you can for some reason be 100% sure that the cast won’t fail (otherwise, having an exception at the problem’s source is much nicer than having to hunt down null-exceptions in some unrelated part of the code) or if the cast is guarded by an if (var is Type)-clause:
Actually, this even improves performance in this benchmark: The if-clause is faster than the “as”-cast and since it always fails, the cast won’t ever be executed. In real-world cases, this obviously (or: hopefully) won’t be the case, though.
#2 by jackson on September 27th, 2010 ·
You’re definitely right that in the real world you’d want to use the result of the cast. After all, that’s why you did the cast in the first place. I left out the assignment so as to only measure the speed of the cast and not the speed of the assignment. With just the assignment (i.e.
bmd = bmd
), I get about 100ms. Since theas
keyword tests are yielding results around 38ms, I’d be skewing the results since more than 2/3 of the reported time being the assignment and 1/3 of it being the cast. In the case of the function call style, only a little under half of the time would be assignment and the remaining part would be the cast time. Since we’ve seen the bytecode generated and I don’t believe the JIT is optimizing the casts out (since the results do vary widely), I think the result is a pretty accurate test.Your
if
check is an interesting idea and I should have mentioned it in the article as an alternative. It’s not technically a cast, but it does yield far better results than function call-style casting that throws an exception:#3 by Till Schneidereit on September 28th, 2010 ·
My wrt actually using the cast’s result was that there’s something more going on than simply added overhead in the loop.
See my edited version of the benchmark here:
http://gist.github.com/601156
As you can see, at least on my machine the loop containing just the “as” cast is exactly as fast as the empty loop, whereas the loop with just the function-style cast takes about 150ms longer.
Compare that to both versions of the loop containing an assignment:
If we factor out the 13ms for the empty loop (which *seems* safe to me), the times for both methods should be:
function-style: 148 + 155 – 13 (assignment + cast – one empty loop) = 290ms
function style: 148 + 13 – 13 (assignment + cast – one empty loop) = 148ms
Instead, not only is the function-style cast significantly faster, the “as” cast is also much slower.
I haven’t looked at the bytecode, but maybe there’s a fused opcode for cast-and-assign or something similar. And maybe in the case of the unused “as” cast, the compiler is actually smart enough for once to detect that the code really doesn’t do anything and eliminates it. I’m using Flex SDK 4.1, so if you used an older version of the SDK, this might explain the difference in results.
#4 by Till Schneidereit on September 28th, 2010 ·
I meant to say “My point wrt actually using […]” of course!
#5 by jackson on September 28th, 2010 ·
Using Flex 4.1, here’s the relevant bytecode from your test of “function call style with assignment”:
It’s still doing the
findpropstrict
/callproperty BitmapData
and then a separatesetslot
, which isn’t present in the “without assignment version”.Here’s the “as cast without guard, with assignment, successful”:
It too is behaving like in the article:
getlex BitmapData
thenastypelate
. Instead of thepop
that happens without assignment,setslot
is used to do the assignment.What’s more interesting than the bytecode to me are your timed results. If the “as without assignment” takes the same time as an empty loop, it would seem that the
as
cast isn’t happening or is taking exceptionally little time to complete. However, when you add in the assignment you should get (for anas
that takes no time) the “assignment only” time. However, you got some extra time, which indicates that something more is going on there. This would seem to say that the JIT, not the compiler, is stripping out theas
cast from when there is no assignment. However, here are my results for your test:In my test (Flash 10.1.85.3), the “as cast without assignment” takes longer than an empty loop, which indicates that the cast is actually happening. The “as cast without guard, with assignment, successful” is also taking longer than just the assignment, which matches yours.
So the difference between our results seems to be that yours is stripping out the
as
cast without assignment and mine is not. I wonder what the difference is between our two environments. What is your exact environment (Flash Player version, browser, CPU, OS, etc.)?#6 by whitered on September 27th, 2010 ·
There is the third case when argument is null, and in that case function call style works 2x faster than as operator on my machine.
#7 by jackson on September 27th, 2010 ·
I still see the
as
keyword as faster, but not by as much:Still, it’s good to know that this case narrows the gap!
#8 by Henke37 on September 27th, 2010 ·
Can be written as:
The later version is hopefully faster.
#9 by jackson on September 27th, 2010 ·
It really depends on what
spr
is. If it’snull
, theif
check will be much quicker since anif
is faster than anas
:Note the last two lines. Since you’re also doing the
if
check, it should be even slower.#10 by k0t0vich on September 27th, 2010 ·
>var result : BitmapData = spr as BitmapData;
>if(spr) ;
m.b.
var result : BitmapData = spr as BitmapData;
if(result ) ;
?
#11 by jackson on September 27th, 2010 ·
Good catch. It’s only the same if
spr
isnull
.#12 by divillysausages on September 28th, 2010 ·
Occasionally, you’re forced to use the function call style, like if you’re converting a ByteArray into XML.
#13 by zlumer on December 2nd, 2010 ·
It’s not a cast. You can’t cast ByteArray to XML, since they are on different inheritance paths. You can only convert one to another. And when you’re using “XML(byteArray)”, you’re not casting, you’re actually using a top-level XML() function, that converts different types of data to XML.
The difference between casting and conversion:
http://en.wikipedia.org/wiki/Type_conversion
#14 by nevir on October 1st, 2010 ·
There is a chance that you will make some review of optimizations that we can benefit from using tools like apparat ( http://code.google.com/p/apparat/ ) in the near future? I would like to read your opinion about it :)
BTW – Your post series about code optimization is great.
#15 by jackson on October 1st, 2010 ·
Perhaps I will. I’ve done a few articles about the popular as3signals library in the past and even developed my own alternative library: TurboSignals.
Thanks for the compliments and the idea!
#16 by Alberto Brealey-Guzmán on October 6th, 2010 ·
I think that the speed difference that is observed when there is no assignment is due to the fact that a typecast with ‘as’ does not really do a typecast, it just checks if the instance is of a given type. Check the ‘astypelate’ entry in the ‘AVM2 Overview’ document by Adobe (http://www.adobemediaplayer.com/content/dam/Adobe/en/devnet/actionscript/articles/avm2overview.pdf). The description reads:
“Pop class and value off of the stack. class should be an object of type Class. If value is of the type specified by class, push value back onto the stack. If value is not of the type specified by class, then push null onto the stack.”
There is no talk about typecasting the value. I guess that when you assign the value then the actual typecast processing takes place.
#17 by jackson on October 6th, 2010 ·
Neither form of cast, in this case at least, is actually changing the type like you would get via a “true” type cast:
intVal = int(numVal)
. Both the function call-style approach and theas
operator are still converting the type of the variable though, which is what I’m testing. Perhaps I used the wrong terminology for that operation. Could you suggest alternate terminology?#18 by Alberto Brealey-Guzmán on October 6th, 2010 ·
What I meant is that the heavy lifting part of casting an object of one type to another is not done with ‘as’. As far as I know, ‘as’ just tells the parser –when compiling– that the assignment is valid, but in runtime if the value is not an instance of the class, then a null will be returned, otherwise the value is returned. This requires less processing time.
When you assign the result of an ‘as’ cast, then the conversion is made, which would explain why it takes longer than just casting without assigning the result.
Does this make sense? I remember someone calling ‘as’ casts as ‘delayed casts’ or something similar.
#19 by jackson on October 6th, 2010 ·
Both function call-style and
as
operator casts tell the compiler that an expression is really of a different type. Since there is no conversion (as in the case ofNumber
toint</code), this is the part I refer to as the cast. At runtime, it's exactly as you state: <code>as
results in the input if the cast succeeds andnull
otherwise. The function call-style cast results in the input if the cast succeeds and an exception/error otherwise. Both definitely take place at runtime and both are definitely happening regardless of whether or not you use the result, via assignment or otherwise.#20 by skyboy on October 21st, 2010 ·
There’s another, probably far less common way of casting:
Which can be sped up when in a function by typing the argument as *. With nJIT on, it could also optimize this, and you seem to have nJIT turned off on your computer.
#21 by jackson on October 21st, 2010 ·
Nice observation! That is indeed another way to cast in AS3. I will either add this casting approach to this article or do a followup.
As for nJIT, what is it? Google searches for “nJIT flash” and “nJIT AS3” are coming up with nothing.
#22 by fastas3 on January 13th, 2011 ·
Really nice article! I made some test with casting performance. Try running your test using non-native class like as subclass of the class you are testing and check the results. It seems that casting native classes is faster using as keyword, but when casting custom class, function style cast is much faster. Example on my blog
#23 by jackson on January 13th, 2011 ·
Glad to see you’ve started up your own blog on AS3 performance. I’m definitely subscribing to your RSS. :)
I converted the test from my article to use a subclass of the test class, as you suggested:
I then changed all instances of
BitmapData
toMyClass
and, of course, the initialization of the test object. I tested using a release build with the release version of the Flash Player 10.1 plugin on a 2.8 Ghz Intel Xeon running on Windows 7 and got the following results:These match the results I got in the article, but are a little faster due to the faster processor. Are you, by chance, using a debug version of the Flash Player?
#24 by fastas3 on January 14th, 2011 ·
Your results confirm that successful function call style cast is faster than successful as keyword cast on subclasses, am I right?
#25 by jackson on January 14th, 2011 ·
It sure seems that way, though I’m not sure why. Perhaps I’ll look into it more in-depth in a followup article.
#26 by fastas3 on January 14th, 2011 ·
Great, I’m looking forward to:)
#27 by fastas3 on January 14th, 2011 ·
Thanks man. You’re right, I’ve used debug version – this could lead me to those odd results. I’ll try to refine my tests using release version of player.
#28 by Matt Bolt on August 13th, 2015 ·
I know this is old, but the discussion resurfaced at work. It’s been a while since Flash 10.1, but the results on 11.X are still very much the same as the original test. However, I wanted to point out that primitives seem much faster using the function style cast (or at least it seems faster using int).
Note that I added assignment and switched the parameter type to Object to ensure that there weren’t any compiler optimizations occurring.
public function CastTest() {
var test:int = 153;
testInt(test);
}
private function testInt(obj:Object):void {
const ITERATIONS:int = 5000000;
var i:int;
var data:BitmapData;
var beforeTime:int;
var afterTime:int;
var result:int;
log(“Cast succeeds:”);
beforeTime = getTimer();
for (i = 0; i < ITERATIONS; ++i)
{
result = int(obj);
}
afterTime = getTimer();
log("\tFunction call style: " + (afterTime-beforeTime));
beforeTime = getTimer();
for (i = 0; i < ITERATIONS; ++i)
{
result = obj as int;
}
afterTime = getTimer();
log("\tAs keyword: " + (afterTime-beforeTime));
}
My results were:
Cast succeeds:
Function call style: 35
As keyword: 90