Implicit Conversion and Performance in Actionscript 3.0

I remember when I started messing with this Flash/AS3 business a while ago, I read about these “new” uint and int types that would supposedly perform better than the “Number” type. THEN I read a bunch of articles by “reputable” people posting about how the performance gains didn’t exist, and which gave seemingly random benchmarks on some tests run using them.

So I looked into it and, barring some crazy patching by Adobe since those original articles were written (over a year ago as of this posting), I’m left to believe that they were just plain wrong. I think that this was due to a misunderstanding of the implicit conversions that happen, even though the authors of these articles *seemed* to be aware of the issue. Who knows.

But here are my results:

UINT time: 90
INT time: 92
NUMBER time: 303

WOW, seems pretty much like what you would expect, huh? Isn’t that just CRAZY?

AS3 has a weak typing system, so you often don’t realize when conversions are being done. Converting takes time. The best you can do is just make sure your types are all homogeneous for a given operation. For example, arrays return their length as “uint”s, so use uints to store array lengths, compare with those lengths, etc. getTimer(), for some reason I don’t understand, returns a regular ‘int’… so operations on values returned from getTimer() should be ints whenever possible (or converted to uint or whatever, and then operated on with other uints/whatever).

Here’s a made up example of how implicit converting can eat your lunch. Please DONT quote me on this as I could, and am likely, wrong about some of this stuff… but to give you an idea of how it MIGHT work, consider this:

var x:uint = 5;
var y:uint = x * 3.5;

Fairly innocuous, huh? Here’s what might happen in this situation:

* The compiler probably thinks ‘3.5′ is a Number type (because of the decimal), so it might decide it needs to promote x to a Number type as well, since the types need to be the same to perform a multiplication (and it’ll promote to Number before it demotes to int, for somewhat obvious reasons).

* It multiplies the promoted x by the 3.5 and gets some value. This value is a Number, of course, since it’s the product of two other Numbers.

* Next, it has to assign this product to our ‘y’ variable, which is a uint. This means that the value obtained by the multiplication has to be converted *again* back to a uint.

So the unaware developer then replaces the x and y values with Number variables and totally eliminates the implicit converting, which increases overall performance. The conclusion reached, then, is that ‘uints are slow and Numbers are fast’, which is completely wrong. Some more might even decide that ‘multiplication is just faster with Numbers’, which is ALSO wrong. Changing our 3.5 to a 3 removes the need for any sort of converting, so the two integers can be multiplied safely and no runtime penalty is incurred.

You could PROBABLY figure out exactly what’s going on with a decompiler, if you feel like doing that. I’m gonna take the fat and sassy route at the moment, though, and just not do it. I’ll take the cop-out “exercise for the reader” excuse for this one ;)

My test results for the example above where like this for the 3.5 value:

Integer (Implicit Conversions): 4192
Number (Implicit Conversions): 3507

And the same exact thing using 3.0 as a value:

Integer (Implicit Conversions): 3422
Number (Implicit Conversions): 3564

It’s easy to just make everything a Number and see performance gains because implicit casting will never happen with Numbers (they’re as high as you can promote in Flash). If you’re careful with making sure your types are used properly, you could see some significant performance gains (look again at the for loop test at the beginning of the post, for example) and, at worst, no performance loss.

The code I used for these tests is:

package
{
	import flash.display.Sprite;
	import flash.utils.getTimer;

	public class TypeBenchmarker extends Sprite
	{
		private static const TOTAL_ITERATIONS_UINT:uint		= 10000000;
		private static const TOTAL_ITERATIONS_INT:int		= 10000000;
		private static const TOTAL_ITERATIONS_NUMBER:Number = 10000000;

		private static const TOTAL_TESTS:uint = 3;

		public function TypeBenchmarker()
		{
			var unsigned_int:uint;
			var signed_int:int;
			var number:Number;

			var start_time:int;
			var time_elapsed:int;

			for (var i:uint = 0; i < TOTAL_TESTS; i++)
			{
				start_time = getTimer();
				for (unsigned_int = 0; unsigned_int < TOTAL_ITERATIONS_UINT; unsigned_int++) { }
				time_elapsed = getTimer() - start_time;
				trace("UINT time: " + time_elapsed);  // Implicit promotion to Number

				start_time = getTimer();
				for (signed_int = 0; signed_int < TOTAL_ITERATIONS_INT; signed_int++) { }
				time_elapsed = getTimer() - start_time;
				trace("INT time: " + time_elapsed); // Implicit promotion to Number

				start_time = getTimer();
				for (number = 0.0; number < TOTAL_ITERATIONS_NUMBER; number++) { }
				time_elapsed = getTimer() - start_time;
				trace("NUMBER time: " + time_elapsed); // Implicit promotion to Number

				/// --- Implicit Casting Example Below ---

				var x:uint;
				var y:uint;
				start_time = getTimer();
				for (unsigned_int = 0; unsigned_int < TOTAL_ITERATIONS_UINT; unsigned_int++)
				{
					x = 5;
					y = x * 3.5;
				}
				time_elapsed = getTimer() - start_time;
				trace("Integer (Implicit Conversions): " + time_elapsed);

				var o:Number;
				var p:Number;
				start_time = getTimer();
				for (unsigned_int = 0; unsigned_int < TOTAL_ITERATIONS_UINT; unsigned_int++)
				{
					o = 5;
					p = o * 3.5;
				}
				time_elapsed = getTimer() - start_time;
				trace("Number (Implicit Conversions): " + time_elapsed);
			}
		}
	}

}

If anyone is bored enough to try this code out, and finds different results, feel free to slap me with ‘em!