"if if"与"if else if"的性能差异

Performance difference of "if if" vs "if else if"

本文关键字:if 性能 else      更新时间:2023-10-16

我只是在想C/c++中这两种语句是否有性能差异:

案例1:

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

案例2:

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

假设简单类型(在本例中,我使用int)并且没有奇怪的业务(没有为int重新定义operator=),至少在AMD64上的GCC 4.6中,没有区别。生成的代码是相同的:

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 

case_1末尾的额外指令只是用于填充(使下一个函数对齐)。

这并不奇怪,在这个函数中找出p没有改变是相当基本的优化。如果p可以更改(例如,按引用传递或指针指向各种do_…函数,或者本身是引用或指针,因此可以有别名),那么行为就不同了,当然生成的代码也会不同。

在前一种情况下,不计算匹配后的条件。

如果其他;如果在最后一个If语句之前找到匹配,则至少跳过最后一个If语句,如果在第一个If语句中找到匹配,则跳过所有其他语句。

if if;即使使用第一个if语句找到了匹配,它也会继续尝试在其他语句中匹配。

是,性能差异是:

第二个语句求每个IF

正如已经证明的那样…它变化。

如果我们谈论的是像int这样的原始(内置)类型,那么编译器可能足够聪明,所以它不重要(或不重要)。但是,在任何情况下,性能影响都很小,因为调用函数的成本比if高得多,所以如果您试图测量它,那么差异可能会在噪声中丢失。

然而,语义是完全不同的。

当我读到第一个case时:

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

那么我就知道,无论这两个分支做什么,都只能执行一个。

然而,当我读到第二个例子时:

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

然后我不得不怀疑是否存在两个分支都被采用的可能性,这意味着我必须仔细检查第一个分支中的代码,以确定它是否可能影响第二个测试。当我最终得出结论它不是的时候,我诅咒这个该死的开发人员,他太懒了,不写该死的else,这将节省我最后10分钟的审查。

因此,可以帮助您自己和您未来的维护者,并集中精力使语义正确和清晰。

在这个问题上,有人可能会争辩说,也许这个调度逻辑可以用其他结构来更好地表达,比如switchmap<int, void()> ?(注意后者,避免过度设计;)

对于如此有限的表达式,您可能不会注意到性能上的任何差异。但从理论上讲,if..if..if要求检查每一个表达式。如果单个表达式是互斥的,可以通过使用if..else if..来节省计算。这样,只有当前面的情况失败时,才会检查另一个表达式。

注意,当检查int是否相等时,也可以只使用switch语句。这样,您仍然可以为长序列的检查保持一定程度的可读性。

switch ( p )
{
    case 0:
        do_this();
        break;
    case 1:
        do_that();
        break;
    case 2:
        do_these():
        break;
}

如果…If情况可以通过在所有后续If检查中使用DONE标志来改进(因为真正的逻辑是从左到右评估的),以避免双重工作/匹配和优化。

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

或者可以使用这样的逻辑:

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

虽然读起来有些混乱(问"为什么这样做")

主要区别在于,一旦其中一个if()返回true, if/else构造将停止计算if()。这意味着它在退出前可能只执行1或2个条件。另一个版本将检查所有3个if,而不管其他的结果。

所以…if/else的操作成本为"最多3次检查"。if/if/if版本的运行成本为"总是做3次检查"。假设被检查的所有3个值的可能性相等,if/else版本将平均执行1.5个if,而if/if版本总是执行3个if。从长远来看,使用"else"构造可以节省1.5 if的CPU时间。