Explicit Type Conversion
Five months ago I said I’d talked about explicit type conversion. I hadn’t, really. What I talked about before was type casts. A cast changes the type, not the data. Today, I’m actually going to talk about type conversion and show you the costs of converting between all of your favorite types: int
, uint
, Number
, Boolean
, String
, and even XML
.
As a refresher, implicit type conversion looks like this:
function foo(intVal:int): void { var numberVal:Number = intVal; // conversion from int to Number }
You simply assign one type to another and the conversion is done for you without the need for any more typing. On the other hand, explicit type conversion looks like this:
function foo(intVal:int): void { var numberVal:Number = Number(intVal); // conversion from int to Number }
In this version, we’re actually calling the top-level Number
function and passing it a value rather than simply relying on a convert_d
bytecode instruction. This is a key difference! As we know, function calls are slow in AS3, so it’s important to avoid them. So why would we ever use them on purpose? Well, as you may remember about implicit type conversion, it’s not allowed for certain types. See the table at the top of the last article for a reference guide. When we need to do a type conversion with a type not allowed implicitly, we must make an explicit type conversion function call.
There’s where today’s test comes in. I’ve adopted the implicit type conversion test and added String
and XML
types since they have explicit conversion functions. The result is this performance-testing app:
package { import flash.display.*; import flash.utils.*; import flash.text.*; public class ExplicitTypeConversion extends Sprite { public function ExplicitTypeConversion() { var logger:TextField = new TextField(); logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); const REPS:int = 10000000; var i:int; var beforeTime:int; var afterTime:int; var loopTime:int; var intVal:int = 1; var uintVal:uint = 1; var numberVal:Number = 1; var booleanVal:Boolean = true; var stringVal:String = "0"; var xmlVal:XML = <xml/>; logger.appendText("Cast Function,int,uint,Number,Boolean,String,XML\n") logger.appendText("int(x),"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { intVal = int(intVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { intVal = int(uintVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { intVal = int(numberVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { intVal = int(booleanVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { intVal = int(stringVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { intVal = int(xmlVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + "\n"); logger.appendText("uint(x),"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { uintVal = uint(intVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { uintVal = uint(uintVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { uintVal = uint(numberVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { uintVal = uint(booleanVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { uintVal = uint(stringVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { uintVal = uint(xmlVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + "\n"); logger.appendText("Number(),"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { numberVal = Number(intVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { numberVal = Number(uintVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { numberVal = Number(numberVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { numberVal = Number(booleanVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { numberVal = Number(stringVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { numberVal = Number(xmlVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + "\n"); logger.appendText("Boolean(),"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { booleanVal = Boolean(intVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { booleanVal = Boolean(uintVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { booleanVal = Boolean(numberVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { booleanVal = Boolean(booleanVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { booleanVal = Boolean(stringVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { booleanVal = Boolean(xmlVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + "\n"); logger.appendText("String(),"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { stringVal = String(intVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { stringVal = String(uintVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { stringVal = String(numberVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { stringVal = String(booleanVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { stringVal = String(stringVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { stringVal = String(xmlVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + "\n"); logger.appendText("XML(),"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { xmlVal = XML(intVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { xmlVal = XML(uintVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { xmlVal = XML(numberVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { xmlVal = XML(booleanVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { xmlVal = XML(stringVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + ","); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { xmlVal = XML(xmlVal); } afterTime = getTimer(); logger.appendText((afterTime-beforeTime) + "\n"); } } }
I ran the performance-testing app in this environment:
- Flex SDK (MXMLC) 4.1.0.16076, compiling in release mode (no debugging or verbose stack traces)
- Release version of Flash Player 10.2.154.25
- 2.4 Ghz Intel Core i5
- Mac OS X 10.6.7
And got these results:
Cast Function | int | uint | Number | Boolean | String | XML |
---|---|---|---|---|---|---|
int(x) | 3 | 2 | 6 | 2 | 72 | 343 |
uint(x) | 2 | 2 | 5 | 2 | 72 | 328 |
Number() | 2 | 3 | 2 | 3 | 69 | 329 |
Boolean() | 2 | 3 | 5 | 2 | 7 | 6 |
String() | 23 | 24 | 28 | 3 | 2 | 31 |
XML() | 2194 | 2185 | 2184 | 2164 | 1949 | 30 |
There are a lot of numbers in those tables, so let’s look at it in graph form:
The graph makes it clear: converting to XML
is tremendously more expensive than any other conversion, except when we’re converting from a String
. It’s hard to understand why this would be since one could always convert from the original type to a String
and then convert the result into an XML
. In any case, such large numbers for XML
obscure the details of the quicker tests, so let’s look at a version of the graph without XML
:
Here we see the more subtle differences shown in high relief. String
, as it turns out, is much more expensive than the other types, except in the Boolean
case. This goes for converting from and to a String
. Remember that the String
in question here is simply "0"
, so it is not processing a long list of characters but instead mostly incurring the overhead of having such a possibility. Once again, it’s hard to tell from this graph what’s going on below the dominance of String
, so let’s remove String
and see the results for the remaining types:
Again we get a closer look at the differences between these types. While there are certainly variations from test to test that mean you should take these results with a grain of salt, the stark difference seems to be the performance of Number
compared to int
, uint
, and Boolean
. Floating point types are clearly more complicated than integer types so you should expect a performance penalty, but here we see that they are roughly twice as expensive in conversion than the other types. Given that there is some loop overhead, it’s likely that this is actually even more dramatic.
So, explicit type conversion really comes down to the type. XML
is much more expensive than anything else, String
follows it, Number
comes third, and the remaining types (int
, uint
, and Boolean
) are all virtually free. What’s notable here is that the function call time seems basically non-existant, which means that the Flash Player must be doing a good job of optimizing it away at the JIT level since it is still present in the bytecode. You’ll still get a few more bytes of SWF-bloating bytecode generated when you use them, but the performance difference is virtually nil these days.
#1 by Jonnie on April 25th, 2011 ·
I’d like to see a comparison between casting and the ‘as’ keyword. I ran a test on it a while back, which led me to use ‘as’ for everything except the types above because of the speed boost. I’m curious if the results have changed in recent versions of Flash Player.
#2 by jackson on April 25th, 2011 ·
I’ve compared the
as
keyword to function-style casting (e.g.BitmapData(someObj)
) before. Here’s the original article and the Flash Player 10.2 followup. Is that what you were looking for?#3 by skyboy on April 25th, 2011 ·
Converting between XML and String are faster than the other types because internally (based on AMF3) they are stored, largely, the same way; I’m sure XML has a dual data structure: both a flat String and a more complex Object layout. Speed comparisons of the various operations on it would reveal more detail. The extra overhead on the other types is likely an oversight or was chosen to be ignored since so few people convert just any type to XML, it’s almost always a String.
I’m curious as to how
as
reacts when passed two primitive types — Does it perform a conversion between int/Number/String? or does it return null? — and how::toString()
, along side!!
for Boolean, performs beside the function casts. I would also wonder about using something likeboolVal = otherVal ? true : false;
if I didn’t already know it would take no less than twice as long as the other tests.#4 by jackson on April 25th, 2011 ·
Interesting that
XML
caches strings for future use. It’s not immediately obvious why this would be helpful, but perhaps with some repeated parsing of constant string literals it would be useful. The lack of optimization for other types leads to an unfortunate optimization in AS3. That is, you can cast faster via two casts.As for the
as
keyword, it really doesn’t do any type conversion, just a cast: