Performance difference of “if if” vs “if else if”

前端 未结 7 1635
遥遥无期
遥遥无期 2020-12-05 17:25

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(         


        
相关标签:
7条回答
  • 2020-12-05 18:05

    Yes the performance difference is:

    The second statement evaluate every IF

    0 讨论(0)
  • 2020-12-05 18:10

    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;
    }
    
    0 讨论(0)
  • 2020-12-05 18:13

    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.

    0 讨论(0)
  • 2020-12-05 18:16

    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.

    0 讨论(0)
  • 2020-12-05 18:17

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

    0 讨论(0)
  • 2020-12-05 18:21

    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 ;) )

    0 讨论(0)
提交回复
热议问题