I’ve been looking at a lot of AVM2 bytecode recently with the excellent Nemo440 AIR app. Some of the code was using my inline Math.ceil() function and I noticed that the int() cast is implemented like any other function call. Today’s article will show you how to optimize the inline Math.ceil() call even further by avoiding this function call.

I’ve shown before how painful function calls are in AS3, so it’s only natural that we would like to avoid them wherever possible in performance-critical code. Given that there is simply a convert_i opcode that is used when assigning a Number to an int but not when explicitly calling int(), we have the framework to do the conversion. Firstly, let’s look at the inline Math.ceil() functions from last time:

function ceilPositiveOnly(value:Number): Number
{
	return value == int(value) ? value : int(value+1);
}
function ceil(value:Number): Number
{
	return value == int(value) ? value : value >= 0 ? int(value+1) : int(value);
}

Both versions have either one or two calls to int(). So let’s re-write with the help of a temporary int variable:

function ceilPositiveOnly(value:Number): Number
{
	var valueInt:int = value;
	if (value == valueInt)
	{
		return value;
	}
	else
	{
		valueInt = value + 1;
		return valueInt;
	}
}
function ceil(value:Number): Number
{
	var valueInt:int = value;
	if (value == valueInt)
	{
		return value;
	}
	else if (value >= 0)
	{
		valueInt = value + 1;
		return valueInt;
	}
	else
	{
		valueInt = value;
		return valueInt;
	}
}

Yes, this version is a good deal longer due to using if-else chains instead of ternary (a ? b : c) operators. Let’s see if it’s worth it with some tests. Firstly, let’s test for correctness to make sure the new version works:

package
{
	import flash.text.*;
	import flash.utils.*;
	import flash.display.*;
 
	public class InlineCeilCorrectness extends Sprite
	{
		private var __logger:TextField;
 
		public function InlineCeilCorrectness()
		{
			__logger = new TextField();
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
 
			log("Positive only:");
			var value:Number;
			var valueInt:int;
			var inlineResult:Number;
			var ceilResult:Number;
			for each (value in [1.1, 2.0])
			{
				valueInt = value;
				if (value == valueInt)
				{
					inlineResult = value;
				}
				else
				{
					valueInt = value + 1;
					inlineResult = valueInt;
				}
				ceilResult = Math.ceil(value);
				log("\t" + value + ": " + inlineResult + " == " + ceilResult + ": " + (inlineResult == ceilResult ? "PASS" : "FAIL"));
			}
			log("Positive and negative:");
			for each (value in [1.1, 2.0, -1.1, -2.0])
			{
				valueInt = value;
				if (value == valueInt)
				{
					inlineResult = value;
				}
				else if (value >= 0)
				{
					valueInt = value + 1;
					inlineResult = valueInt;
				}
				else
				{
					valueInt = value;
					inlineResult = valueInt;
				}
				ceilResult = Math.ceil(value);
				log("\t" + value + ": " + inlineResult + " == " + ceilResult + ": " + (inlineResult == ceilResult ? "PASS" : "FAIL"));
			}
		}
 
		private function log(msg:*): void
		{
			__logger.appendText(msg + "\n");
		}
	}
}

We seem to get passing marks here:

Positive only:
	1.1: 2 == 2: PASS
	2: 2 == 2: PASS
Positive and negative:
	1.1: 2 == 2: PASS
	2: 2 == 2: PASS
	-1.1: -1 == -1: PASS
	-2: -2 == -2: PASS

Now let’s see if all this work has paid off. Here is a test to measure the performance:

package
{
	import flash.text.*;
	import flash.utils.*;
	import flash.display.*;
 
	public class InlineCeilPerformance extends Sprite
	{
		private var __logger:TextField;
 
		public function InlineCeilPerformance()
		{
			__logger = new TextField();
			__logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(__logger);
 
			const ITERATIONS:int = 50000000;
			var i:int;
			var res:Number;
			var before:int = getTimer();
			var value:Number = 0.0;
			var valueInt:int;
			for (i = 0; i < ITERATIONS; ++i)
			{
				valueInt = value;
				if (value == valueInt)
				{
					res = value;
				}
				else if (value >= 0)
				{
					valueInt = value + 1;
					res = valueInt;
				}
				else
				{
					valueInt = value;
					res = valueInt;
				}
				value += 1.0;
			}
			log("Inline (positive only): " + (getTimer()-before));
 
			before = getTimer();
			value = 0.0;
			for (i = 0; i < ITERATIONS; ++i)
			{
				valueInt = value;
				if (value == valueInt)
				{
					res = value;
				}
				else if (value >= 0)
				{
					valueInt = value + 1;
					res = valueInt;
				}
				else
				{
					valueInt = value;
					res = valueInt;
				}
				value += 1.0;
			}
			log("Inline (positive and negative): " + (getTimer()-before));
 
			before = getTimer();
			value = 0.0;
			for (i = 0; i < ITERATIONS; ++i)
			{
				res = Math.ceil(value);
				value += 1.0;
			}
			log("Math.ceil(): " + (getTimer()-before));
		}
 
		private function log(msg:*): void
		{
			__logger.appendText(msg + "\n");
		}
	}
}

For your reference, here are the results from last time:

Environment Inline (positive) Inline (positive and negative) Math.ceil()
2.2 Ghz Intel Core 2 Duo, Mac OS X 10.6 869 844 1520

And with the new and improved version that doesn’t use int(), we get these results:

Environment Inline (positive) Inline (positive and negative) Math.ceil()
2.2 Ghz Intel Core 2 Duo, Mac OS X 10.6 428 (+103%) 429 (+97%) 1520

Hooray! It seems as though we can successfully work around the slowness of the int() function call to achieve better performance in this case. We seem to be have taken the very slow Math.ceil(), inlined it to double the speed, and then removed int() to double the speed again. At this point we are four times faster than the built-in version! There are probably many other cases where avoiding int() would be a good idea. If you’re looking to speed up any performance-critical code, try searching through for any calls to int().