【问题标题】:Why are ternary and logical operators more efficient than if branches?为什么三元和逻辑运算符比 if 分支更有效?
【发布时间】:2015-08-17 18:31:06
【问题描述】:

我偶然发现了this question/answer,它提到在大多数语言中,逻辑运算符如:

x == y && doSomething();

可以比使用if 分支做同样的事情更快:

if(x == y) {
  doSomething();
}

同样地,它表示三元运算符:

x = y == z ? 0 : 1

通常比使用if 分支更快:

if(y == z) {
  x = 0;
} else {
  x = 1;
}

这让我在谷歌上搜索,这让我找到了this fantastic answer,它解释了分支预测。

基本上,它说的是 CPU 以非常快的速度运行,而不是放慢速度来计算每个 if 分支,它试图猜测会发生什么结果并将适当的指令放入其管道中。但如果猜错了,它就必须备份并重新计算相应的指令。

但这仍然没有向我解释为什么逻辑运算符或三元运算符的处理方式与if 分支不同。由于 CPU 不知道x == y 的结果,它是否还必须猜测是否将调用doSomething()(因此,所有doSomething 的代码)放入其管道?因此,如果它的猜测不正确,是否支持?同样,对于三元运算符,CPU在确定x中存储的内容时是否应该猜测y == z是否会评估为真,如果猜测错误则备份?

我不明白为什么编译器对分支的处理方式与其他任何条件语句不同。不应该以相同的方式评估所有条件吗?

【问题讨论】:

  • 也许它必须做一些事情,第一个语句是表达式,另一个是代码块,意思是 jmp。
  • 这更像是一个编译器问题而不是 CPU 问题。以这种方式使用时,三元运算符在功能上几乎与分支相同。但是由于某些原因,MSVC 和 GCC 等主要编译器在使用三元运算符时更有可能发出条件分支。
  • 布尔运算符通常编译为分支。 CPU 无法区分。但是,一些分支也被编译成无分支指令。取决于优化器的质量。如果我们在这里谈论 C,编译器会为您处理。

标签: performance operation branch-prediction


【解决方案1】:

简短的回答 - 它根本不是。虽然帮助分支预测可以提高您的性能 - 将其用作逻辑语句的一部分不会更改已编译的代码。 如果你想帮助分支预测,请使用 __builtin_expect (for GNU)

为了强调,让我们比较一下编译器的输出:

#include <stdio.h>


int main(){
        int foo;

        scanf("%d", &foo); /*Needed to eliminate optimizations*/

#ifdef IF       
        if (foo)
                printf("Foo!");
#else
        foo &&  printf("Foo!");
#endif 
        return 0;
}

对于 gcc -O3 branch.c -DIF 我们得到:

0000000000400540 <main>:
  400540:       48 83 ec 18             sub    $0x18,%rsp
  400544:       31 c0                   xor    %eax,%eax
  400546:       bf 68 06 40 00          mov    $0x400668,%edi
  40054b:       48 8d 74 24 0c          lea    0xc(%rsp),%rsi
  400550:       e8 e3 fe ff ff          callq  400438 <__isoc99_scanf@plt>
  400555:       8b 44 24 0c             mov    0xc(%rsp),%eax
  400559:       85 c0                   test   %eax,%eax #This is the relevant part
  40055b:       74 0c                   je     400569 <main+0x29>
  40055d:       bf 6b 06 40 00          mov    $0x40066b,%edi
  400562:       31 c0                   xor    %eax,%eax
  400564:       e8 af fe ff ff          callq  400418 <printf@plt>
  400569:       31 c0                   xor    %eax,%eax
  40056b:       48 83 c4 18             add    $0x18,%rsp
  40056f:       c3                      retq 

对于 gcc -O3 branch.c

0000000000400540 <main>:
  400540:       48 83 ec 18             sub    $0x18,%rsp
  400544:       31 c0                   xor    %eax,%eax
  400546:       bf 68 06 40 00          mov    $0x400668,%edi
  40054b:       48 8d 74 24 0c          lea    0xc(%rsp),%rsi
  400550:       e8 e3 fe ff ff          callq  400438 <__isoc99_scanf@plt>
  400555:       8b 44 24 0c             mov    0xc(%rsp),%eax
  400559:       85 c0                   test   %eax,%eax
  40055b:       74 0c                   je     400569 <main+0x29>
  40055d:       bf 6b 06 40 00          mov    $0x40066b,%edi
  400562:       31 c0                   xor    %eax,%eax
  400564:       e8 af fe ff ff          callq  400418 <printf@plt>
  400569:       31 c0                   xor    %eax,%eax
  40056b:       48 83 c4 18             add    $0x18,%rsp
  40056f:       c3                      retq 

这是完全相同的代码。

您链接到的问题衡量 JAVAScript 的性能。请注意,对于这两种情况,它可能被解释为不同的东西(因为 Java 脚本被解释或 JIT 取决于版本)。 无论如何,JavaScript 并不是学习性能的最佳选择。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-03
    • 2012-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多