我刚刚在 gcc 上测试了一个简单的例子。
对于 x86,这似乎由优化器处理并取决于优化级别。尽管我猜这里的正确答案是“这取决于编译器”。
生成的代码取决于 CPU。一些CPU(我立即想到sparc64,但我确信还有其他CPU)在条件分支指令上有标志,告诉CPU如何预测它,因此编译器根据构建生成“预测真/预测假”指令在编译器的规则和代码的提示中(例如__builtin_expect
).
英特尔在这里记录了他们的行为:https://software.intel.com/en-us/articles/branch-and-loop-reorganization-to-prevent-mispredicts。简而言之,Intel CPU 上的行为是,如果 CPU 之前没有有关分支的信息,它将预测不太可能采用前向分支,而可能采用后向分支(考虑循环与错误处理)。
这是一些示例代码:
int bar(int);
int
foo(int x)
{
if (__builtin_expect(x>10, PREDICTION))
return bar(10);
return 42;
}
编译(我使用 omit-frame-pointer 使输出更具可读性,但我仍然在下面清理它):
$ cc -S -fomit-frame-pointer -O0 -DPREDICTION=0 -o 00.s foo.c
$ cc -S -fomit-frame-pointer -O0 -DPREDICTION=1 -o 01.s foo.c
$ cc -S -fomit-frame-pointer -O2 -DPREDICTION=0 -o 20.s foo.c
$ cc -S -fomit-frame-pointer -O2 -DPREDICTION=1 -o 21.s foo.c
00.s 和 01.s 之间没有区别,因此这表明这取决于优化(至少对于 gcc 而言)。
这是(清理后的)生成的 20.s 代码:
foo:
cmpl $10, %edi
jg .L2
movl $42, %eax
ret
.L2:
movl $10, %edi
jmp bar
这是 21.s:
foo:
cmpl $10, %edi
jle .L6
movl $10, %edi
jmp bar
.L6:
movl $42, %eax
ret
正如预期的那样,编译器重新排列了代码,以便我们不希望采用的分支在前向分支中完成。