有没有可能告诉分支预测器跟随分行的可能性有多大?

先说清楚,我在这里不是为了任何形式的可移植性,所以任何将我绑定到某个盒子的解决方案都是可以的。

基本上,我有一个 if 语句,它99% 的时间都会计算为 true,而且我正在努力争取每一个最后的性能时钟,我可以发出某种编译器命令(使用 gcc4.1.2和 x86ISA,如果有必要的话)来告诉分支预测器它应该缓存该分支吗?

23076 次浏览

是的,http://kerneltrap.org/node/4705

__builtin_expect是一种方法 Gcc (版本 > = 2.96) 程序员指示分支 预测信息 的返回值 __builtin_expect是第一个参数(它只能是一个整数) 传给了它。

if (__builtin_expect (x, 0))
foo ();


[This] would indicate that we do not expect to call `foo', since we
expect `x' to be zero.

没有因为没有集结指挥部让分支预测器知道。别担心,分支预测器很聪明。

此外,必须对过早优化以及它如何邪恶进行评论。

编辑: Drakosha 提到了 GCC 的一些宏。但是,我相信这是一个代码优化,实际上与分支预测没有任何关系。

在我看来,这听起来有点过头了——这种类型的优化将节省很少的时间。例如,使用更现代的 gcc 版本将对优化产生更大的影响。另外,尝试启用和禁用所有不同的优化标志; 它们并不都能提高性能。

基本上,与许多其他富有成效的道路相比,这似乎极不可能产生任何重大差异。

编辑: 谢谢你的评论。我已经建立了这个社区的维基,但留下它,以便其他人可以看到的评论。

SUN C Studio 为此定义了一些实用程序。

# 杂注很少 _ 调用()

如果条件表达式的一部分是函数调用,或者以函数调用开始,这种方法就可以工作。

但是没有办法标记一般的 if/while 语句

奔腾4(又名网爆微架构)将分支预测器提示作为 jcc 指令的前缀,但只有 p4使用过它们。 参见 http://ref.x86asm.net/geek32.html 第3.5节的阿格纳雾的优秀高分选择指南 ,从 http://www.agner.org/optimize/。他也有一本关于 C + + 优化的指南。

前面和后面的 x86 CPU 都默认忽略这些前缀字节。是否有任何性能测试结果用于可能/不可能的提示?提到 PowerPC 有一些跳转指令,其中有一个分支预测提示作为编码的一部分。这是一个相当罕见的建筑特征。在编译时静态预测分支是非常困难的,所以通常最好让硬件来解决这个问题。

关于最新的 Intel 和 AMD CPU 中的分支预测器和分支-目标-缓冲区的确切行为,官方发布的信息不多。优化手册(很容易在 AMD 和英特尔的网站上找到)提供了一些建议,但没有记录具体的行为。有些人运行测试来预测实现,例如 Core2有多少 BTB 条目... ... 不管怎样,显式提示预测器的想法已经被抛弃了(现在)。

例如,Core2有一个分支历史缓冲区,如果循环总是运行一个常数较少的迭代(< 8或16 IIRC) ,这个缓冲区可以避免错误地预测循环退出。但是不要太快展开,因为一个循环适合在64字节(或19uops 上 Penryn)将不会有指令获取瓶颈,因为它从缓冲区重放... 去读 Agner Fog 的 pdf,他们是 好极了

另见 为什么英特尔这些年来改变了静态分支预测机制?: 因为 Sandybridge 根本不使用静态预测,所以我们可以从性能实验中看出来,这些性能实验试图对 CPU 的工作进行逆向工程。(当动态预测失败时,许多较老的 CPU 使用静态预测作为后备。通常的静态预测是不采用前向分支,而采用后向分支(因为后向分支通常是循环分支)


使用 GNU C 的 __builtin_expectlikely()/unlikely()宏的效果(就像 Drakosha 的回答中提到的那样)使得 没有直接将 BP 提示插入到高潮 中。(在 gcc -march=pentium4中可能会这样做,但是在编译其他内容时不会)。

实际效果是布局代码,使得快速路径具有更少的已采用分支,也许总指令更少。这将有助于在静态预测发挥作用的情况下进行分支预测(例如,动态预测器是冷的,在确实退回到静态预测的 CPU 上,而不是仅仅让分支在预测器缓存中彼此别名)

有关 code-gen 的特定示例,请参见 在 if else 语句中,GCC _ _ builtin _ Expect 的优势是什么?

即使预测完美,被取走的树枝也比没有被取走的树枝花费稍微多一点。当 CPU 获取16字节的代码块并行解码时,获取的分支意味着该获取块中的后续指令不是要执行的指令流的一部分。它在前端产生气泡,这可能成为高吞吐量代码的瓶颈(它不会因为缓存丢失而在后端停滞,而且具有很高的指令层级平行)。

在不同的块之间跳转也有可能触及更多的代码 缓存行,增加 L1i 缓存占用,如果是冷的,可能会导致更多的指令缓存丢失。(以及可能的 uop-cache 内存占用)。因此,这是另一个优势,有快速路径是短期和线性。


GCC 的配置文件引导的优化通常使得可能的/不太可能的宏变得不必要。编译器收集运行时数据,每个分支以何种方式进行代码布局决策,并识别热块/函数和冷块/函数。(例如,它会在热函数中展开循环,而不会在冷函数中展开循环。)参见 -fprofile-generate-fprofile-use如何在 g + + 中使用配置文件引导的优化?

否则 GCC 必须使用各种启发式方法来猜测,如果您没有使用可能的/不太可能的宏,也没有使用 PGO。默认情况下,-fguess-branch-probability-O1或更高级别启用。

Https://www.phoronix.com/scan.php?page=article&item=gcc-82-PGO&num=1 在至强可伸缩服务器 CPU 上使用 gcc8.2对 PGO 和普通 PGO 进行了基准测试。(Skylake-AVX512).每个基准测试都至少有一个小小的加速,有些测试获得了约10% 的收益。(其中大部分可能来自热循环中的循环展开,但也有一些可能来自更好的分支布局和其他效果。)

是的,但它会有 没有效应。例外情况是在 Netburst 之前的较老(过时)架构,即使那样它也不能做任何可测量的事情。

有一个“分支提示”的操作码英特尔引入了网爆体系结构,和一个默认的静态分支预测冷跳(向后预测采取,向前预测不采取)在一些旧的体系结构。GCC 使用 __builtin_expect (x, prediction)实现这一点,其中预测通常为0或1。 编译器发出的操作码是所有较新的处理器体系结构(> = Core 2)上的 被忽略了。这个小角落的例子实际上做了一些事情,这是对旧的 Netburst 架构的一个冷跃进的例子。Intel 现在建议不要使用静态分支提示,可能是因为他们认为代码大小的增加比可能的边际加速更有害。

除了预测器无用的分支提示之外,__builtin_expect也有它的用途,编译器可以重新排序代码以提高缓存使用率或节省内存。

有很多原因导致它不能像预期的那样工作。

  • 处理器可以完美地预测小循环(n < 64)。
  • 处理器可以很好地预测小的重复模式(n ~ 7)。
  • 处理器本身可以在运行时比编译器/程序员在编译时更好地估计分支的概率。
  • 一个分支的 可预测性(= 一个分支被正确预测的概率)比一个分支被采取的概率重要得多。不幸的是,这是高度依赖于架构的,并且预测分支的可预测性是出了名的困难。

阅读更多关于 Agner Fogs 手册分支预测的内部工作。 参见 gcc邮件列表

我建议与其担心分支预测,不如分析代码并优化代码以减少分支的数量。一个例子是循环展开,另一个例子是使用布尔编程技术而不是使用 if语句。

大多数处理器喜欢预取语句。通常,分支语句会在处理器内生成一个 错误,使其刷新预取队列。这是最大的惩罚。为了减少这种处罚时间,请重写(并设计)代码,以便可用的分支更少。此外,一些处理器可以有条件地执行指令而不必进行分支。

通过使用循环展开和大型 I/O 缓冲区,我已经将程序的执行时间从1小时优化为2分钟。在这种情况下,分支预测不会节省很多时间。