34

I was just thinking is there any performance difference between the 2 statements in C/C++:

Case 1:

if (p==0)
   do_this();
else if (p==1)
   do_that();
else if (p==2)
   do_these():

Case 2:

if(p==0)
    do_this();
if(p==1)
    do_that();
if(p==2)
    do_these();
Christian Rau
  • 45,360
  • 10
  • 108
  • 185
Syntax_Error
  • 5,964
  • 15
  • 53
  • 73

8 Answers8

39

Assuming simple types (in this case, I used int) and no funny business (didn't redefine operator= for int), at least with GCC 4.6 on AMD64, there is no difference. The generated code is identical:

0000000000000000 <case_1>:                                   0000000000000040 <case_2>:
   0:   85 ff                   test   %edi,%edi               40:   85 ff                   test   %edi,%edi
   2:   74 14                   je     18 <case_1+0x18>        42:   74 14                   je     58 <case_2+0x18>
   4:   83 ff 01                cmp    $0x1,%edi               44:   83 ff 01                cmp    $0x1,%edi
   7:   74 27                   je     30 <case_1+0x30>        47:   74 27                   je     70 <case_2+0x30>
   9:   83 ff 02                cmp    $0x2,%edi               49:   83 ff 02                cmp    $0x2,%edi
   c:   74 12                   je     20 <case_1+0x20>        4c:   74 12                   je     60 <case_2+0x20>
   e:   66 90                   xchg   %ax,%ax                 4e:   66 90                   xchg   %ax,%ax
  10:   f3 c3                   repz retq                      50:   f3 c3                   repz retq 
  12:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)        52:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
  18:   31 c0                   xor    %eax,%eax               58:   31 c0                   xor    %eax,%eax
  1a:   e9 00 00 00 00          jmpq   1f <case_1+0x1f>        5a:   e9 00 00 00 00          jmpq   5f <case_2+0x1f>
  1f:   90                      nop                            5f:   90                      nop
  20:   31 c0                   xor    %eax,%eax               60:   31 c0                   xor    %eax,%eax
  22:   e9 00 00 00 00          jmpq   27 <case_1+0x27>        62:   e9 00 00 00 00          jmpq   67 <case_2+0x27>
  27:   66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)        67:   66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
  2e:   00 00                                                  6e:   00 00 
  30:   31 c0                   xor    %eax,%eax               70:   31 c0                   xor    %eax,%eax
  32:   e9 00 00 00 00          jmpq   37 <case_1+0x37>        72:   e9 00 00 00 00          jmpq   77 <case_2+0x37>
  37:   66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
  3e:   00 00 

The extra instruction at the end of case_1 is just for padding (to get the next function aligned).

This isn't really surprising, figuring out that p isn't changed in that function is fairly basic optimization. If p could be changed (e.g., passed-by-reference or pointer to the various do_… functions, or was a reference or pointer itself, so there could be an alias) then the behavior is different, and of course the generated code would be too.

Community
  • 1
  • 1
derobert
  • 49,731
  • 15
  • 94
  • 124
22

In the former case conditions after the one matched are not evaluated.

Michael Krelin - hacker
  • 138,757
  • 24
  • 193
  • 173
  • 3
    The man speaks the truth. In a worst case scenario, they'll be the same. Otherwise if-else-if will be faster since as soon as one condition is satisfied, the remaining will not be evaluated. – Ayush Nov 01 '11 at 17:26
  • It's not that I mind, but what could be the possible reason for a downvote here? I run out of ideas. – Michael Krelin - hacker Nov 01 '11 at 17:28
  • 2
    @xbonez: Never underestimate compilers... I wouldn't be surprised if both pieces of code ended up with the same assembly (in C, where `==` cannot be overloaded) as the compiler could infer that only one of the conditions can be true (assuming that the *if-branch* does not modify the value being tested) That is, assuming that the result of both versions is the same and types are basic types, the compiler can convert both into a switch. – David Rodríguez - dribeas Nov 01 '11 at 17:34
  • 5
    Don't overestimate compilers, they don't even know if the code has the same meaning. – Michael Krelin - hacker Nov 01 '11 at 17:37
  • 2
    Good point - what if each block is allowed to modify `p`... (and try proving that a pointer to `p` hasn't been stored on a disk somewhere and can be retrieved by those functions). – Kerrek SB Nov 01 '11 at 17:39
  • @Kerrek: escape-analysis can often prove no such pointer has been stored, although obviously with plenty of cases where in fact it hasn't, but the analysis can't prove it. If the compiler can inline `do_this()` then it can attack the question from the other end, which is why strict aliasing rules matter -- if `p` is an `int` whereas `do_this()` only modifies `float` variables via pointers, the compiler is permitted to assume that `do_this()` doesn't modify `p`. – Steve Jessop Nov 01 '11 at 17:40
  • Even in C if you marked `p` as `volatile` the compiler would be forced to compile the code quite differently in each case. Or indeed if those functions had memory indicators like fences, etc. Simply put Michael is right, these is a different logical sequence of code. Performance aside, the coder should first determine which is the _correct_ sequence of code. – edA-qa mort-ora-y Nov 01 '11 at 17:49
  • @DavidRodríguez-dribeas `==` cannot be overloaded for builtin types in C++ either. – L. F. May 07 '19 at 10:21
18

if else is faster; if a match was found before the last if then at least the last if statement is skipped, if match was found in first it will skip all other statements.

if if is slower; even if a match was found using the first if statement, it will keep on trying to match in other statement.

Waqleh
  • 9,741
  • 8
  • 65
  • 103
  • 5
    What? The accepted answer, written 2 years before this one, already shows that the compiler realizes that if `if (p == 0)` is true `p` cannot also have some other value, so those tests are skipped. – Bo Persson Apr 04 '17 at 12:34
  • 3
    @BoPersson my answer is logical that do not apply to a single specific programming language or hardware as the accepted answer does. – Waqleh Apr 04 '17 at 12:48
  • `if` blocks with returns work the same as if-else. – its4zahoor Apr 26 '22 at 23:49
  • I suspect that the language optimizer reads that block and creates a switch statement. – Derek Wade Aug 16 '23 at 22:41
8

Yes the performance difference is:

The second statement evaluate every IF

Joris Meys
  • 106,551
  • 31
  • 221
  • 263
5

As it has already been demonstrated... it varies.

If we are talking about primitive (built-ins) types like int, then the compiler may be smart enough so that it does not matter (or not). In any case though, the performance impact will be minor because the cost of calling a function is much higher than that of a if, so the difference will probably get lost in the noise if you ever attempt to measure it.

The semantics, however, are quite different.

When I read the first case:

if (...) {
  // Branch 1
} else if (...) {
  // Branch 2
}

Then I know that no matter what the two branches might do, only one can ever be executed.

However, when I read the second case:

if (...) {
}
if (...) {
}

Then I have to wonder whether there is a possibility that both branches be taken or not, which mean that I have to scrutinize the code in the first to determine whether it is likely to influence the second test or not. And when I finally conclude it's not, I curse the bloody developer who was too lazy to write that damn else that would have saved me the last 10 minutes of scrutiny.

So, help yourself and your future maintainers, and concentrate on getting the semantics right and clear.

And on this subject, one could argue that perhaps this dispatch logic could be better express with other constructs, such as a switch or perhaps a map<int, void()> ? (beware of the latter and avoid over-engineering ;) )

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
3

You probably won’t notice any difference in performance for such a limited number of expressions. But theoretically, the if..if..if requires to check every single expression. If single expressions are mutally exclusive, you can save that evaluation by using if..else if.. instead. That way only when the previous cases fail, the other expression is checked.

Note that when just checking an int for equality, you could also just use the switch statement. That way you can still maintain some level of readability for a long sequence of checks.

switch ( p )
{
    case 0:
        do_this();
        break;

    case 1:
        do_that();
        break;

    case 2:
        do_these():
        break;
}
poke
  • 369,085
  • 72
  • 557
  • 602
1

If..If case could be improved by using a DONE flag in all subsequent IF checks (since true logic is evaluated left-right), to avoid double work/match and optimization.

bool bDone = false;

If( <condition> ) {
    <work>;
    bDone = true;
}
if (!bDone && <condition> ) {
    <work>;
    bDone = true;
}

Or could use some logic like this:

While(true) {
    if( <condition> ) {
        <work>;
        break;
    }
    if( <condition> ) {
        <work>;
        break;
    }
    ....
    break;
}

though it's somewhat confusing to read (ask "why do this way")

Derek Wade
  • 697
  • 8
  • 11
0

The major difference is that the if/else construct will stop evaluating the ifs() once one of them returns a true. That means it MAY only execute 1 or 2 of the ifs before bailing. The other version will check all 3 ifs, regardless of the outcome of the others.

So.... if/else has a operational cost of "Up to 3 checks". The if/if/if version has an operational cost of "always does 3 checks". Assuming all 3 of the values being checked are equally likely, the if/else version will have an average of 1.5 ifs performed, while the if/if one will always do 3 ifs. In the long term, you're saving yourself 1.5 ifs worth of CPU time with the "else" construct.

Marc B
  • 356,200
  • 43
  • 426
  • 500
  • 2
    "always does 3 checks" - unless the optimizer is clever enough to prove to its own satisfaction that the value of `p` doesn't change during `do_this()` or `do_that()`. Then in the if/if case, and assuming no overloaded operators, it could realize that if `p==0` is true then `p==1` can't possibly also be true, and skip evaluating it, just as if the code was if/else. The abstract machine performs 3 checks, but whether the implementation does is another matter. – Steve Jessop Nov 01 '11 at 17:35