我刚在gcc上进行了一个简单的测试。
对于x86,这似乎由优化器处理,并且取决于优化级别。尽管我猜正确的答案是“这取决于编译器”。
生成的代码是CPU相关的。一些CPU(例如sparc64)在条件分支指令上有标志,告诉CPU如何预测它,因此编译器根据编译器内置规则和代码提示(例如__builtin_expect
)生成“预测为真/预测为假”指令。
英特尔在这里说明了它们的行为:https://software.intel.com/en-us/articles/branch-and-loop-reorganization-to-prevent-mispredicts。简而言之,在英特尔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
正如预期的那样,编译器重新排列了代码,以便我们不期望执行的分支是在前向分支中完成的。