GCC 的 __builtin_expect 在 if else 语句中的优势是什么?

新手上路,请多包涵

我遇到了一个 #define 他们使用 __builtin_expect

文档 说:

内置函数: long __builtin_expect (long exp, long c)

您可以使用 __builtin_expect 为编译器提供分支预测信息。一般来说,您应该更喜欢为此使用实际的配置文件反馈( -fprofile-arcs ),因为众所周知,程序员不善于预测他们的程序实际执行情况。但是,有些应用程序很难收集这些数据。

返回值为 exp 的值,应该是整数表达式。内置的语义是期望 exp == c 。例如:

>        if (__builtin_expect (x, 0))
>         foo ();
>
> ```
>
> 将表明我们不希望调用 `foo` ,因为我们希望 `x` 为零。

那么为什么不直接使用:

if (x) foo ();

”`

而不是 __builtin_expect 的复杂语法?

原文由 RajSanpui 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 836
2 个回答

想象一下将从以下位置生成的汇编代码:

 if (__builtin_expect(x, 0)) {
    foo();
    ...
} else {
    bar();
    ...
}

我想它应该是这样的:

   cmp   $x, 0
  jne   _foo
_bar:
  call  bar
  ...
  jmp   after_if
_foo:
  call  foo
  ...
after_if:

您可以看到指令的排列顺序是 bar 案例在 foo 案例之前(与 C 代码相反)。这可以更好地利用 CPU 流水线,因为跳转会破坏已经获取的指令。

在执行跳转之前,它下面的指令( bar 案例)被推送到管道。由于 foo 情况不太可能发生,因此也不太可能发生跳跃,因此不太可能破坏管道。

原文由 Blagovest Buyukliev 发布,翻译遵循 CC BY-SA 3.0 许可协议

让我们反编译看看 GCC 4.8 用它做了什么

Blagovest 提到了分支反转来改进管道,但是当前的编译器真的做到了吗?让我们来了解一下!

没有 __builtin_expect

 #include "stdio.h"
#include "time.h"

int main() {
    /* Use time to prevent it from being optimized away. */
    int i = !time(NULL);
    if (i)
        puts("a");
    return 0;
}

使用 GCC 4.8.2 x86_64 Linux 编译和反编译:

 gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o

输出:

 0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       75 0a                   jne    1a <main+0x1a>
  10:       bf 00 00 00 00          mov    $0x0,%edi
                    11: R_X86_64_32 .rodata.str1.1
  15:       e8 00 00 00 00          callq  1a <main+0x1a>
                    16: R_X86_64_PC32       puts-0x4
  1a:       31 c0                   xor    %eax,%eax
  1c:       48 83 c4 08             add    $0x8,%rsp
  20:       c3                      retq

内存中的指令顺序没有改变:首先是 puts 然后是 retq 返回。

__builtin_expect

现在将 if (i) 替换为:

 if (__builtin_expect(i, 0))

我们得到:

 0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       74 07                   je     17 <main+0x17>
  10:       31 c0                   xor    %eax,%eax
  12:       48 83 c4 08             add    $0x8,%rsp
  16:       c3                      retq
  17:       bf 00 00 00 00          mov    $0x0,%edi
                    18: R_X86_64_32 .rodata.str1.1
  1c:       e8 00 00 00 00          callq  21 <main+0x21>
                    1d: R_X86_64_PC32       puts-0x4
  21:       eb ed                   jmp    10 <main+0x10>

puts 被移到函数的最后, retq 返回!

新代码基本相同:

 int i = !time(NULL);
if (i)
    goto puts;
ret:
return 0;
puts:
puts("a");
goto ret;

此优化未使用 -O0 完成。

但是祝你好运,编写一个使用 __builtin_expect 运行速度更快的示例,那时 CPU 真的很聪明。我的天真尝试 就在这里

C++20 [[likely]][[unlikely]]

C++20 已经标准化了那些 C++ 内置函数: 如何在 if-else 语句中使用 C++20 的可能/不太可能属性 他们很可能(双关语!)做同样的事情。

原文由 Ciro Santilli OurBigBook.com 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题