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:

Explicit Type Conversion Performance (all)

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:

Explicit Type Conversion Performance (no 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:

Explicit Type Conversion Performance (no XML or String)

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.