【问题标题】:Performance difference of "if if" vs "if else if"“if if”与“if else if”的性能差异
【发布时间】:2011-11-01 17:22:58
【问题描述】:

我只是在想 C/C++ 中的 2 个语句之间是否存在性能差异:

案例一:

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

【问题讨论】:

  • 运行基准测试是理解事物如何运作的现代替代品?
  • 不仅是性能,而且意义完全不同!想象一下p 重载operator== 总是返回true,而do_that() 将你所有的钱都寄给你的前妻。
  • 只需担心编写清晰、简洁、健壮和可靠的代码 - 担心诸如此类的微小的微优化会适得其反且毫无意义。
  • @PaulR 虽然总的来说你是对的,但这个问题仍然是一个有效且有趣的问题,并且更深入地了解计算机的真正作用绝不是毫无意义的(只要不会适得其反)因为你不要让这些问题成为你的主要编码指南,OP 在这里没有暗示)。
  • 如果人们花在抨击“微优化”上的所有时间都花在编写清晰、简洁、健壮和可靠的代码上,那它也将是“微优化”的。

标签: c++ c


【解决方案1】:

假设简单类型(在这种情况下,我使用了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 末尾的额外指令仅用于padding (to get the next function aligned)

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

【讨论】:

  • 无论如何都无法为int 重新定义operator==(或operator=)...
【解决方案2】:

在前一种情况下,匹配后的条件不被评估。

【讨论】:

  • 男人说的是实话。在最坏的情况下,它们将是相同的。否则 if-else-if 会更快,因为只要满足一个条件,就不会评估其余条件。
  • 这不是我介意的,但是在这里投反对票的可能原因是什么?我没有主意了。
  • @xbonez: 永远不要低估编译器...如果两段代码最终使用相同的程序集(在 C 中,== 不能重载),我不会感到惊讶,因为编译器可以推断只有一个条件可以为真(假设 if-branch 没有修改被测试的值)即假设两个版本的结果相同并且类型是基本类型,编译器可以将两者都转换为开关。
  • 不要高估编译器,他们甚至不知道代码是否具有相同的含义。
  • 好点 - 如果允许每个块修改 p...(并尝试证明指向 p 的指针尚未存储在某个磁盘上,并且可以通过以下方式检索)这些功能)。
【解决方案3】:

if else 更快;如果在最后一个 if 之前找到匹配项,则至少跳过最后一个 if 语句,如果在第一个找到匹配项,它将跳过所有其他语句。

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

【讨论】:

  • 什么?接受的答案,在这个答案之前 2 年写的,已经表明编译器意识到如果 if (p == 0) 为真,p 也不能有其他值,所以这些测试被跳过。
  • @BoPersson 我的回答是合乎逻辑的,并不像公认的答案那样适用于单一特定的编程语言或硬件。
【解决方案4】:

是的,性能差异是:

第二个语句评估每个 IF

【讨论】:

    【解决方案5】:

    对于如此有限数量的表达式,您可能不会注意到任何性能差异。但理论上,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;
    }
    

    【讨论】:

      【解决方案6】:

      正如已经证明的那样......它会有所不同。

      如果我们谈论像int 这样的原始(内置)类型,那么编译器可能足够聪明,因此它并不重要(或不重要)。但无论如何,性能影响都会很小,因为调用函数的成本远高于 if,因此如果您尝试测量差异,可能会在噪音中消失。

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

      当我读到第一个案例时:

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

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

      但是,当我阅读第二种情况时:

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

      然后我想知道是否有可能同时采用两个分支,这意味着我必须仔细检查第一个代码以确定它是否可能影响第二个测试。当我最终得出结论时,我诅咒该死的开发人员懒得写那个该死的else,这可以为我节省最后 10 分钟的审查时间。

      所以,帮助你自己和你未来的维护者,并专注于使语义正确和清晰。

      关于这个问题,有人可能会争辩说,也许这种调度逻辑可以用其他构造更好地表达,例如 switchmap&lt;int, void()&gt; ? (提防后者,避免过度设计;))

      【讨论】:

        【解决方案7】:

        主要区别在于 if/else 构造将在其中一个返回 true 时停止评估 ifs()。这意味着它可以在保释之前只执行 1 或 2 个 if。另一个版本将检查所有 3 个 if,而不管其他的结果如何。

        所以.... if/else 的运营成本为“最多 3 次检查”。 if/if/if 版本的运营成本为“总是进行 3 次检查”。假设被检查的所有 3 个值的可能性相同,则 if/else 版本平均执行 1.5 个 if,而 if/if 始终执行 3 个 if。从长远来看,使用“else”构造可以为自己节省 1.5 个 ifs 的 CPU 时间。

        【讨论】:

        • "总是进行 3 次检查" - 除非优化器足够聪明,足以证明p 的值在do_this()do_that() 期间不会改变。然后在 if/if 情况下,假设没有重载运算符,它可以意识到如果p==0 为真,那么p==1 不可能也为真,并跳过评估它,就像代码是 if/else .抽象机执行 3 次检查,但实现是否执行是另一回事。
        【解决方案8】:

        If..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;
        }
        

        虽然读起来有些混乱(问“为什么要这样”)

        【讨论】:

          猜你喜欢
          • 2013-05-01
          • 1970-01-01
          • 2012-01-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-09-21
          相关资源
          最近更新 更多