【发布时间】:2017-06-12 08:33:01
【问题描述】:
从我的大学课程中,我听说,按照惯例,最好将更可能的条件放在 if 中而不是 else 中,这可能有助于 static 分支预测器。例如:
if (check_collision(player, enemy)) { // very unlikely to be true
doA();
} else {
doB();
}
可以改写为:
if (!check_collision(player, enemy)) {
doB();
} else {
doA();
}
我找到了一篇博文Branch Patterns, Using GCC,对这个现象进行了更详细的解释:
为 if 语句生成前向分支。理由 使它们不太可能被采取的是处理器可以采取 分支之后的指令的优势 指令可能已经放置在指令缓冲区内 指令单元。
在它旁边,它说(强调我的):
在编写 if-else 语句时,总是使“then”块更多 比 else 块更可能被执行,所以处理器可以采取 已经放置在取指中的指令的优势 缓冲区。
最后,有一篇文章,Intel 写的,Branch and Loop Reorganization to Prevent Mispredicts,总结了两条规则:
当没有收集到数据时使用静态分支预测 微处理器遇到分支时,通常是 第一次遇到分支。规则很简单:
- 前向分支默认为不采用
- 后向分支默认为采用
为了有效地编写代码以利用这些 规则,在编写 if-else 或 switch 语句时,检查最 首先是常见情况,然后逐步处理到最不常见的情况。
据我了解,这个想法是流水线 CPU 可以遵循指令缓存中的指令,而不会通过跳转到代码段中的另一个地址来破坏它。不过,我知道,在现代 CPU 微架构的情况下,这可能会被过度简化。
但是,GCC 似乎不遵守这些规则。给定代码:
extern void foo();
extern void bar();
int some_func(int n)
{
if (n) {
foo();
}
else {
bar();
}
return 0;
}
它生成(带有-O3 -mtune=intel的版本6.3.0):
some_func:
lea rsp, [rsp-8]
xor eax, eax
test edi, edi
jne .L6 ; here, forward branch if (n) is (conditionally) taken
call bar
xor eax, eax
lea rsp, [rsp+8]
ret
.L6:
call foo
xor eax, eax
lea rsp, [rsp+8]
ret
我发现强制执行所需行为的唯一方法是使用__builtin_expect 重写if 条件,如下所示:
if (__builtin_expect(n, 1)) { // force n condition to be treated as true
所以汇编代码会变成:
some_func:
lea rsp, [rsp-8]
xor eax, eax
test edi, edi
je .L2 ; here, backward branch is (conditionally) taken
call foo
xor eax, eax
lea rsp, [rsp+8]
ret
.L2:
call bar
xor eax, eax
lea rsp, [rsp+8]
ret
【问题讨论】:
-
stackoverflow.com/q/109710/905902linux内核使用宏(都是__builtin_expect)来使用条件分支的先验知识。
-
现代 Intel CPU 不使用静态分支预测。我也不认为 GCC 在任何地方都承诺将 if/else 语句的“真实”子句视为最有可能的替代方案。你应该使用
__builtin_expect,就像提到的wildplasser,告诉它哪个更有可能。或者更好的是,配置文件引导优化。 -
参见 Anger Fog 的微架构手册。第 3.16 节“PM 和核心 2 中的静态预测”:“这些处理器不使用静态预测。预测器仅在第一次看到分支时进行随机预测,具体取决于分配给的 BTB 条目中发生的情况新的分支。”。 agner.org/optimize
-
即使在一个完整的程序中,它也不太重要。除非您使用仅具有静态预测功能的处理器,否则大多数跳跃将被动态预测。
-
出于某种原因,gcc 的 profile_estimate pass 猜测 n 有 54% 的机会为 0...(请参阅
-fdump-tree-all-all)通常它有一个启发式 == 更有可能是错误的,但它没有'似乎没有在这里使用。你可以把它提交到 gcc 的 bugzilla 上询问它。请注意,如果您使用-fprofile-generate编译,然后运行您的程序,然后使用-fprofile-use重新编译,gcc 将可以访问真实的统计数据并做出更好的决策。
标签: c gcc assembly x86 branch-prediction