kin*_*er1 130 c linux gcc built-in
我遇到了#define他们使用的一个__builtin_expect.
文件说:
内置功能:
long __builtin_expect (long exp, long c)您可以使用
__builtin_expect为编译器提供分支预测信息.一般来说,你应该更喜欢使用实际的配置文件反馈(-fprofile-arcs),因为程序员在预测程序实际执行情况方面是非常糟糕的.但是,有些应用程序难以收集此数据.返回值是值
exp,它应该是一个整数表达式.内置的语义是预期的exp == c.例如:Run Code Online (Sandbox Code Playgroud)if (__builtin_expect (x, 0)) foo ();表示我们不打算打电话
foo,因为我们预计x会为零.
那么为什么不直接使用:
if (x)
foo ();
Run Code Online (Sandbox Code Playgroud)
而不是复杂的语法__builtin_expect?
Bla*_*iev 167
想象一下将从以下代码生成的汇编代码:
if (__builtin_expect(x, 0)) {
foo();
...
} else {
bar();
...
}
Run Code Online (Sandbox Code Playgroud)
我想它应该是这样的:
cmp $x, 0
jne _foo
_bar:
call bar
...
jmp after_if
_foo:
call foo
...
after_if:
Run Code Online (Sandbox Code Playgroud)
您可以看到指令的排列顺序是bar案例在案例之前foo(而不是C代码).这可以更好地利用CPU流水线,因为跳转会使已经取出的指令崩溃.
在执行跳转之前,它下面的指令(bar情况)被推送到管道.由于foo案件不太可能,因此不太可能跳楼,因此不大可能打破管道.
Cir*_*四事件 42
让我们反编译看看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;
}
Run Code Online (Sandbox Code Playgroud)
使用GCC 4.8.2 x86_64 Linux编译和反编译:
gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o
Run Code Online (Sandbox Code Playgroud)
输出:
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
Run Code Online (Sandbox Code Playgroud)
内存中的指令顺序没有改变:首先puts然后retq返回.
同 __builtin_expect
现在替换if (i)为:
if (__builtin_expect(i, 0))
Run Code Online (Sandbox Code Playgroud)
我们得到:
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>
Run Code Online (Sandbox Code Playgroud)
将puts被转移到功能,的最末端retq的回报!
新代码基本相同:
int i = !time(NULL);
if (i)
goto puts;
ret:
return 0;
puts:
puts("a");
goto ret;
Run Code Online (Sandbox Code Playgroud)
这种优化没有完成-O0.
但是,写一个运行速度__builtin_expect比没有运行得更快的例子,祝你好运,那些时候CPU非常聪明.我天真的尝试就在这里.
Mic*_*hne 41
这个想法__builtin_expect是告诉编译器你通常会发现表达式的计算结果为c,这样编译器就可以针对这种情况进行优化.
我猜有人认为他们很聪明,并且他们通过这样做加快了速度.
不幸的是,除非情况得到很好的理解(很可能他们没有做过这样的事情),否则可能会让事情变得更糟.文档甚至说:
一般来说,你应该更喜欢使用实际的配置文件反馈(
-fprofile-arcs),因为程序员在预测程序实际执行情况方面是非常糟糕的.但是,有些应用程序难以收集此数据.
一般情况下,您不应该使用,__builtin_expect除非:
Ker*_* SB 13
好吧,正如它在描述中所说的那样,第一个版本在构造中添加了一个预测元素,告诉编译器x == 0分支是更可能的分支 - 也就是说,它是你的程序将更频繁地使用的分支.
考虑到这一点,编译器可以优化条件,以便在预期条件成立时需要最少量的工作,代价是在意外情况下可能需要做更多的工作.
查看在编译阶段以及在生成的程序集中如何实现条件,以查看一个分支如何比另一个分支更少工作.
但是,如果有问题的条件是一个被调用很多的紧密内循环的一部分,我只希望这个优化有明显的效果,因为结果代码的差异相对较小.如果你以错误的方式优化它,你可能会降低你的表现.
| 归档时间: |
|
| 查看次数: |
46494 次 |
| 最近记录: |