【发布时间】:2017-01-19 00:07:50
【问题描述】:
开发人员可以使用__builtin_expect builtin 来帮助编译器understand 分支可能去向。
将来,我们可能会为此目的获得standard attribute,但截至今天,至少所有clang、icc 和gcc 都支持非标准的__builtin_expect。
但是,icc 在您使用时似乎会生成异常糟糕的代码1。也就是说,无论预测的方向如何,使用内置函数的代码都比没有它的代码更糟糕。
以下面的玩具函数为例:
int foo(int a, int b)
{
do {
a *= 77;
} while (b-- > 0);
return a * 77;
}
在三个编译器中,icc 是唯一一个将其编译为 3 条指令的 optimal scalar loop:
foo(int, int):
..B1.2: # Preds ..B1.2 ..B1.1
imul edi, edi, 77 #4.6
dec esi #5.12
jns ..B1.2 # Prob 82% #5.18
imul eax, edi, 77 #6.14
ret
gcc 和 Clang 都可以轻松解决问题并使用 5 条指令。
另一方面,当您在循环条件上使用 likely 或 unlikely 宏时,icc 完全是脑残:
#define likely(x) __builtin_expect((x), 1)
#define unlikely(x) __builtin_expect((x), 0)
int foo(int a, int b)
{
do {
a *= 77;
} while (likely(b-- > 0));
return a * 77;
}
这个循环在功能上等同于前一个循环(因为__builtin_expect 只返回它的第一个参数),但是icc produces some awful code:
foo(int, int):
mov eax, 1 #9.12
..B1.2: # Preds ..B1.2 ..B1.1
xor edx, edx #9.12
test esi, esi #9.12
cmovg edx, eax #9.12
dec esi #9.12
imul edi, edi, 77 #8.6
test edx, edx #9.12
jne ..B1.2 # Prob 95% #9.12
imul eax, edi, 77 #11.15
ret #11.15
该函数的大小翻了一番,达到 10 条指令,并且(更糟糕的是!)关键循环增加了一倍多,达到 7 条指令,其中包含一个很长的关键依赖链,涉及 cmov 和其他奇怪的东西。
如果您使用 unlikely hint 以及 Godbolt 支持的所有 icc 版本(13、14、17),情况也是如此。因此,无论提示如何,无论实际运行时行为如何,代码生成都会变得更糟。
gcc 和 clang 在使用提示时都不会受到任何影响。
这是怎么回事?
1 至少在我尝试的第一个和后续示例中。
【问题讨论】:
标签: c optimization x86 icc built-in