Logical operators are necessary in every app, so it’s unfortunate that they are so slow in AS3. That’s why I was happy to see a potential alternative in a recent comment by Skyboy. Today’s article shows you how to do logic faster by avoiding logical operators (e.g. &&) and using their bitwise (e.g. &) operator counterparts instead.

First thing’s first, this is going to lead to some funky-looking code:

// instead of this...
result = operand1 && operand2;
 
// do this...
result = Boolean(int(operand1) & int(operand2));
 
// if operand1 and operand2 are not Booleans, do this...
result = Boolean(int(operand1 != null) & int(operand2 != null));

So you’re going to sacrifice some readability and do some extra typing if you want to use this method to improve your app’s speed. But improving your app’s speed is what this site is all about, so let’s do some performance testing to see if this funky code is worth it.

The following performance test simply does a bunch of statements like the above with Boolean variables equaling false and true and Sprite variables equaling null and not-null. For each of these variables, the app does 1, 5, and 10 &&/& operations.

package
{
	import flash.display.*;
	import flash.utils.*;
	import flash.text.*;
 
	public class BitwiseLogic extends Sprite
	{
		private var __logger:TextField = new TextField();
		private function row(...cols): void
		{
			__logger.appendText(cols.join(",")+"\n");
		}
 
		public function BitwiseLogic()
		{
			var logger:TextField = __logger;
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			var beforeTime:int;
			var afterTime:int;
			var logicalTime:int;
			const REPS:int = 100000000;
			var i:int;
			var FALSE:Boolean = false;
			var TRUE:Boolean = true;
			var NULL:Sprite;
			var NOTNULL:Sprite = this;
			var bool:Boolean;
 
			row("Test", "&& Time", "& Time");
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = FALSE && FALSE;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(int(FALSE) & int(FALSE));
			}
			afterTime = getTimer();
			row("1-false", logicalTime, (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = FALSE && FALSE && FALSE &&
				       FALSE && FALSE && FALSE;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(
					int(FALSE) & int(FALSE) & int(FALSE) &
					int(FALSE) & int(FALSE) & int(FALSE)
				);
			}
			afterTime = getTimer();
			row("5-false", logicalTime, (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = FALSE && FALSE && FALSE &&
				       FALSE && FALSE && FALSE &&
				       FALSE && FALSE && FALSE &&
				       FALSE && FALSE;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(
					int(FALSE) & int(FALSE) & int(FALSE) &
					int(FALSE) & int(FALSE) & int(FALSE) &
					int(FALSE) & int(FALSE) & int(FALSE) &
					int(FALSE) & int(FALSE)
				);
			}
			afterTime = getTimer();
			row("10-false", logicalTime, (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = TRUE && TRUE;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(int(TRUE) & int(TRUE));
			}
			afterTime = getTimer();
			row("1-true", logicalTime, (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = TRUE && TRUE && TRUE &&
				       TRUE && TRUE && TRUE;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(
					int(TRUE) & int(TRUE) & int(TRUE) &
					int(TRUE) & int(TRUE) & int(TRUE)
				);
			}
			afterTime = getTimer();
			row("5-true", logicalTime, (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = TRUE && TRUE && TRUE &&
				       TRUE && TRUE && TRUE &&
				       TRUE && TRUE && TRUE &&
				       TRUE && TRUE;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(
					int(TRUE) & int(TRUE) & int(TRUE) &
					int(TRUE) & int(TRUE) & int(TRUE) &
					int(TRUE) & int(TRUE) & int(TRUE) &
					int(TRUE) & int(TRUE)
				);
			}
			afterTime = getTimer();
			row("10-true", logicalTime, (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = NULL && NULL;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(int(NULL!=null) & int(NULL!=null));
			}
			afterTime = getTimer();
			row("1-null", logicalTime, (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = NULL && NULL && NULL &&
				       NULL && NULL && NULL;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(
					int(NULL!=null) & int(NULL!=null) & int(NULL!=null) &
					int(NULL!=null) & int(NULL!=null) & int(NULL!=null)
				);
			}
			afterTime = getTimer();
			row("5-null", logicalTime, (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = NULL && NULL && NULL &&
				       NULL && NULL && NULL &&
				       NULL && NULL && NULL &&
				       NULL && NULL;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(
					int(NULL!=null) & int(NULL!=null) & int(NULL!=null) &
					int(NULL!=null) & int(NULL!=null) & int(NULL!=null) &
					int(NULL!=null) & int(NULL!=null) & int(NULL!=null) &
					int(NULL!=null) & int(NULL!=null)
				);
			}
			afterTime = getTimer();
			row("10-null", logicalTime, (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = NOTNULL && NOTNULL;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(int(NOTNULL!=null) & int(NOTNULL!=null));
			}
			afterTime = getTimer();
			row("1-not null", logicalTime, (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = NOTNULL && NOTNULL && NOTNULL &&
				       NOTNULL && NOTNULL && NOTNULL;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(
					int(NOTNULL!=null) & int(NOTNULL!=null) & int(NOTNULL!=null) &
					int(NOTNULL!=null) & int(NOTNULL!=null) & int(NOTNULL!=null)
				);
			}
			afterTime = getTimer();
			row("5-not null", logicalTime, (afterTime-beforeTime));
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = NOTNULL && NOTNULL && NOTNULL &&
				       NOTNULL && NOTNULL && NOTNULL &&
				       NOTNULL && NOTNULL && NOTNULL &&
				       NOTNULL && NOTNULL;
			}
			afterTime = getTimer();
			logicalTime = afterTime - beforeTime;
 
			beforeTime = getTimer();
			for (i = 0; i < REPS; ++i)
			{
				bool = Boolean(
					int(NOTNULL!=null) & int(NOTNULL!=null) & int(NOTNULL!=null) &
					int(NOTNULL!=null) & int(NOTNULL!=null) & int(NOTNULL!=null) &
					int(NOTNULL!=null) & int(NOTNULL!=null) & int(NOTNULL!=null) &
					int(NOTNULL!=null) & int(NOTNULL!=null)
				);
			}
			afterTime = getTimer();
			row("10-not null", logicalTime, (afterTime-beforeTime));
		}
	}
}

I ran the performance test with the following environment:

  • Flex SDK (MXMLC) 4.5.1.21328, compiling in release mode (no debugging or verbose stack traces)
  • Release version of Flash Player 10.3.181.35
  • 2.4 Ghz Intel Core i5
  • Mac OS X 10.7.0

And got these results:

Test && Time & Time
1-false 241 237
5-false 304 236
10-false 1018 231
1-true 238 243
5-true 304 248
10-true 506 234
1-null 240 245
5-null 596 227
10-null 1034 241
1-not null 242 232
5-not null 862 227
10-not null 1431 242

False Performance Graph

True Performance Graph

Null Performance Graph

Not-Null Performance Graph

As the graphs make exceedingly clear, the bitwise versions start out just as fast as their logical counterparts but then remain totally flat as the number of operands rises. This is in stark contrast to the logical operator versions that scale more-or-less linearly as the number of operands increases. So at least there’s no need to use this bitwise trick with just one operator, but it really helps out as you pile on more and more operators.

Why do we see the above performance? To find out, I made some simple test functions to inspect their bytecode:

private function bitwiseBoolean(val:Boolean, bool:Boolean): void
{
	bool = Boolean(int(val) & int(val));
}
 
private function logicalBoolean(val:Boolean, bool:Boolean): void
{
	bool = val && val;
}

Here’s the bytecode you get for these: (with annotations by me)

// bitwiseLogical
getlocal1     	            // get val (first operand)
convert_b     	            // convert val to a Boolean
dup           	            // duplicate val
iffalse       	L1          // if Boolean(val) is false, go to block L1
 
pop                           // pop val off stack
getlocal1     	            // get val (second operand)
convert_b     	            // convert val to a Boolean
 
L1:
convert_b     	            // convert val to a Boolean (again)
setlocal2     	            // set Boolean(val) to the bool parameter
 
 
 
// bitwiseBoolean
findpropstrict	Boolean     // get the top-level Boolean function
findpropstrict	int         // get the top-level int function
getlocal1     	            // get val (first operand)
callproperty  	int (1)     // call int(val) [first operand]
findpropstrict	int         // get the top-level int function
getlocal1     	            // get val (second operand)
callproperty  	int (1)     // call int(val) [second operand]
bitand        	            // int(val) & int(val)
callproperty  	Boolean (1) // call Boolean(int(val) & int(val))
convert_b     	            // convert the result to a Boolean
setlocal2     	            // set the result to the bool parameter

The key difference is that the bitwise operator version does not jump to any blocks. Essentially, the logical operator version is doing an if check for every operator but the bitwise operator version is simply doing bitwise arithmetic.

In conclusion, if you’re using more than one logical operator per statement in performance-critical code (e.g. looping over a large game map or 3D meshes), you may want to switch some of these statements to using bitwise operators. You could see a 7x performance increase!

Any other tips for speeding up logic in AS3? Let’s discuss it in the comments!

Share on FacebookShare on TwitterShare on LinkedinShare on Google+