If-Else is Really Expensive
Sometimes a tiny amount of code costs a huge amount of performance. This is especially true of built-in language features, which many programmers assume to be extremely cheap if not free. Today we’ll look at if
and see just how much performance it can cost your app. Read on to see!
Today’s article is a follow-up to an article about AS3 from 2014. AS3 had a lot of issues related to performance, so it’s good to validate with another language to make sure it wasn’t some weird quirk related to Flash. In this case, we’ll port that test to C# and see if we can replicate the results.
To check it out, let’s run two very similar loops. Both of them just compute a simple sum, but one of them has an if
. Here’s how those loops look:
// No "if" int sum = 0; for (var i = 0; i < REPS; ++i) { int cur = i & 1; sum += cur; } // Includes an "if" sum = 0; for (int i = 0; i < REPS; ++i) { int cur = i & 1; if (cur > 0) { sum += cur; } else { sum += cur; } }
Notice that both the if
and else
cases do the same work: adding to the sum. So the result will be the same for both loops.
Now let’s check the timing for those loops with a very large REPS
value to see how long they take. Here’s the full script:
using UnityEngine; public class TestScript : MonoBehaviour { private string report; void Start() { const int REPS = 1000000000; var sw = System.Diagnostics.Stopwatch.StartNew(); int sum = 0; for (var i = 0; i < REPS; ++i) { int cur = i & 1; sum += cur; } var noBranchTime = sw.ElapsedMilliseconds; sw.Reset(); sw.Start(); sum = 0; for (int i = 0; i < REPS; ++i) { int cur = i & 1; if (cur > 0) { sum += cur; } else { sum += cur; } } var branchTime = sw.ElapsedMilliseconds; report = "Operation,Time\n" + "No Branch," + noBranchTime + "\n" + "Branch," + branchTime; } void OnGUI() { GUI.TextArea(new Rect(0, 0, Screen.width, Screen.height), report); } }
If you want to try out the test yourself, simply paste the above code into a TestScript.cs
file in your Unity project’s Assets
directory and attach it to the main camera game object in a new, empty project. Then build in non-development mode for 64-bit processors and run it windowed at 640×480 with fastest graphics. I ran it that way on this machine:
- 2.3 Ghz Intel Core i7-3615QM
- Mac OS X 10.11.3
- Unity 5.3.4f1, Mac OS X Standalone, x86_64, non-development
- 640×480, Fastest, Windowed
And here are the results I got:
Operation | Time |
---|---|
No Branch | 696 |
Branch | 873 |
The version with the if-else
block took 25% longer than the version without one. Again, both versions result in the same sum, so the if-else
block isn’t adding anything to the output.
There are multiple potential reasons for this performance discrepancy. First, there is an actual cost to do the if
: comparisons and jumps. Second, modern CPUs attempt to predict which instructions will be executed and the presence of an if
means that it’ll be wrong sometimes. In the case of this test, it’s wrong half the time and that can diminish the performance of the version with if
.
The simple takeaway from this test is to remember that two simple letters—if
—can carry a huge performance cost. Avoid branching with if-else
or switch
in performance-critical code if you can manage it. Let me know in the comments if you’ve found any performance boosts like this, such as by removing an if
!
#1 by Zach on March 21st, 2016 ·
I was expecting a different analysis. it seems like you compared the cost of using if to the cost of doing nothing. Every operation fails that test, as code you don’t run is always faster than code you do.
What do you think would be a valid comparison for the relative expense of an if statement?
#2 by jackson on March 21st, 2016 ·
Thanks for pointing this out. It’s a subtle difference but an important one that I didn’t really explain in the article.
The idea of the test was to show the cost of the
if
. To that end I made two loops that have the same result with only one of them containing an additionalif
. It’s true that it should take longer than the version without theif
and that was the point. I could add more code to the no-if
version but then I’d be comparing the cost of theif
to the cost of some arbitrary other code. In the process, I’d also lose what I set out to measure: the cost of theif
. In any case, since there were two comments about this I added some more code to show a relative difference against some arbitrary other code. See below for that.#3 by FH on March 21st, 2016 ·
Somehow the test is not really fair. The first loop has only two instructions, while the second one has three. If you add a meaningless “bool b = cur > 0;” to the first loop, the difference might be less extreme.
#4 by jackson on March 21st, 2016 ·
Since this was the second comment about the fairness of the test, I decided to add some extra code to the “no-
if
” version as a way to judge the cost of theif
against other code. See above for my answer about why I didn’t include such code in the first place.In any case, I followed your advice and added the
bool b = cur > 0;
statement to the “no-if
” version. That increased its time to a huge value, somewhere in the 2000-3000 range. Since that’s clearly way more expensive than theif
with the same conditional, I dropped it. Clearly something much more expensive is going on there. Instead, I put in anothersum += cur;
statement and got 792 milliseconds on the same test computer.So at least compared to a
+=
operation, theif
is still more expensive. It definitely makes the difference less extreme.#5 by Barliesque on May 19th, 2016 ·
Presumably the added cost lies in storing the resulting value into the variable b, which doesn’t happen in the “if” version.
#6 by benjamin guihaire on September 7th, 2017 ·
did you test with IL2CPP ?
#7 by jackson on September 7th, 2017 ·
No, this is an old test before I started posting IL2CPP results.