The if-else keyword is not free. So, how expensive is it? Today’s article finds out.

For lots of background on why if-else and other so-called “branch” instructions can be costly, see the Wikipedia article on branch prediction. In short, the CPU is trying to predict which branch of your code to take. When you write an if-else, there are two sets of code that it might execute.

Now to find out how expensive the branch is. Let’s look at two loops that do the same thing: compute the sum of a series of numbers. It’s pretty useless in the real world, but it serves to show the cost of branching pretty well. Here’s the first loop that doesn’t us branching:

private function noBranch(reps:int): int
{
	var sum:int;
	for (var i:int; i < reps; ++i)
	{
		var cur:int = i & 1;
		sum += cur;
	}
	return sum;
}

At each iteration, the low bit of the loop value i is retrieved and added to the sum. That’s it. It’s very straightforward. Now, let’s throw in an if-else:

private function branch(reps:int): int
{
	var sum:int;
	for (var i:int; i < reps; ++i)
	{
		var cur:int = i & 1;
		if (cur > 0)
		{
			sum += cur;
		}
		else
		{
			sum += cur;
		}
	}
	return sum;
}

This loop also takes the low bit of i and adds it to sum, but there’s a pointless if-else thrown in so we’ll have something to measure. Notice that both the true (if) and false (else) cases do the exact same thing as each other and as the previous function.

Now that we’ve set this up, here’s a little test app that calls each function with a high reps value.

package
{
	import flash.display.*;
	import flash.utils.*;
	import flash.text.*;
 
	public class BranchCost extends Sprite
	{
		private var logger:TextField = new TextField();
		private function row(...cols): void
		{
			logger.appendText(cols.join(",") + "\n");
		}
 
		public function BranchCost()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			init();
		}
 
		private function init(): void
		{
			const REPS:int = 1000000000;
			var i:int;
			var j:int;
			var beforeTime:int;
			var afterTime:int;
			var sum:int;
 
			row("Operation", "Time");
 
			beforeTime = getTimer();
			sum = noBranch(REPS);
			afterTime = getTimer();
			row("No Branch", (afterTime-beforeTime));
 
			beforeTime = getTimer();
			sum = branch(REPS);
			afterTime = getTimer();
			row("Branch", (afterTime-beforeTime));
		}
 
		private function noBranch(reps:int): int
		{
			var sum:int;
			for (var i:int; i < reps; ++i)
			{
				var cur:int = i & 1;
				sum += cur;
			}
			return sum;
		}
 
		private function branch(reps:int): int
		{
			var sum:int;
			for (var i:int; i < reps; ++i)
			{
				var cur:int = i & 1;
				if (cur > 0)
				{
					sum += cur;
				}
				else
				{
					sum += cur;
				}
			}
			return sum;
		}
	}
}

I tested this app using the following environment:

  • Release version of Flash Player 13.0.0.206
  • 2.3 Ghz Intel Core i7-3615QM
  • Mac OS X 10.9.2
  • Google Chrome 34.0.1847.131
  • ASC 2.0.0 build 354130 (-debug=false -verbose-stacktraces=false -inline -optimize=true)

And got these results:

Operation Time
No Branch 2080
Branch 2321

Branch Cost Performance Graph

The version with the branch (if-else) is taking about 12% longer to perform the same task. While this isn’t a real-world case, there are plenty of examples where you should consider removing a branching statement. For example, you might think you have an optimization by not adding zero to a sum:

if (val != 0)
{
	sum += val;
}

However, it’s likely that the if-else is taking longer than just adding zero. Since adding zero has no effect, why not just do it and save some time?

Try to keep branch instructions in mind while writing performance-critical code. They’re often necessary, but never free. You just may realize a nice speedup if you can find a way around using them.

Spot a bug? Have a question or suggestion? Post a comment!