是否可以告诉分支预测变量跟随分支的可能性有多大?


74

为了清楚起见,我在这里不打算进行任何形式的可移植性,因此任何将我与特定机器绑定在一起的解决方案都可以。

基本上,我有一条if语句,它将在99%的时间中将其评估为true,并试图提高性能的最后一个时钟,我是否可以发出某种编译器命令(使用GCC 4.1.2和x86 ISA,如果重要)告诉分支预测器它应该为该分支缓存?


12
使用配置文件引导的优化进行编译(-fprofile-generate,在某些测试数据上运行,-fprofile-use)。然后,gcc将知道每个分支的统计信息,并能够为快速路径优化地布置代码。但是,如果在没有PGO的情况下编译代码,则buildin_expect对于有帮助的地方仍然是一个好主意。Linux内核为此具有一些良好的宏(例如,可能性(和不可能)),因为很难为内核生成配置文件数据。
彼得·科德斯

Answers:


60

是。http://kerneltrap.org/node/4705

__builtin_expect是gcc(版本> = 2.96)为程序员提供的一种方法,用于向编译器指示分支预测信息。返回值 __builtin_expect是传递给它的第一个参数(只能是整数)。


9
在Microsoft环境中,如果语句被预测为始终为true。某些版本确实具有配置文件引导的优化。
Charles Beattie


75

是的,但是没有效果。例外是Netburst之前的较旧(过时)体系结构,即使如此,它也无法做任何可衡量的事情。

英特尔在Netburst架构中引入了一个“分支提示”操作码,在某些较旧的架构上,默认的静态分支预测用于冷跳跃(向后预测采用,向前预测未采用)。GCC使用来实现此目标__builtin_expect (x, prediction),其中预测通常为0或1。所有较新的处理器体系结构(> = Core 2)都会忽略编译器发出的操作码。实际起作用的小角落情况是旧的Netburst架构发生冷跳的情况。英特尔现在建议不要使用静态分支提示,这可能是因为他们认为增加代码大小比提高边际速度有害得多。

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

有多种原因导致其无法正常运行。

  • 处理器可以完美预测小循环(n <64)。
  • 处理器可以完美地预测小的重复模式(n〜7)。
  • 处理器本身可以比编译期间的编译器/编程器更好地估计运行时分支的概率。
  • 分支的可预测性(=分支将被正确预测的概率)比采用分支的概率重要得多。不幸的是,这是高度依赖于体系结构的,并且众所周知,很难预测分支的可预测性。

在Agner Fogs手册上阅读有关分支预测的内部工作的更多信息。另请参阅gcc邮件列表


3
如果您可以引述/指向在较新的体系结构上忽略提示的确切部分,那将很好。
2009年

6
我给出的链接中的第3.12章“静态预测”。
甘瑟·皮兹

当您说较小的循环可以完美地预测时,这是否意味着循环必须完成一次(可能会错误地预测边缘),然后让所有迭代都能在下次执行循环时完美地进行预测?
KenArrari '19

31

奔腾4(又名Netburst微体系结构)具有分支预测器提示作为jcc指令的前缀,但是只有P4会对它们执行任何操作。参见http://ref.x86asm.net/geek32.html。以及 来自 http://www.agner.org/optimize/的Agner Fog优秀的asm opt指南的第3.5节。他也有C ++优化指南。

早期和以后的x86 CPU静默忽略那些前缀字节。 是否有针对可能/不太可能使用提示的性能测试结果?提到PowerPC有一些跳转指令,这些指令在编码中包含分支预测提示。这是一个非常罕见的建筑功能。在编译时静态地预测分支是非常困难的,因此通常最好将其留给硬件来解决。

关于最新的Intel和AMD CPU中的分支预测器和分支目标缓冲区的确切运行方式的正式发布并不多。优化手册(可以在AMD和Intel的网站上轻松找到)提供了一些建议,但没有记录特定的行为。有人进行了测试以试图弄清实现,例如Core2有多少BTB条目...无论如何,明确暗示预测变量的想法已经被放弃(目前)。

例如,所记录的内容是Core2具有分支历史记录缓冲区,如果循环始终运行恒定的短迭代次数(<8或16 IIRC),则可以避免错误预测循环退出。但是不要太快展开,因为适合64字节(或Penryn上为19uops)的循环不会有指令获取瓶颈,因为它可以从缓冲区中重播...阅读Agner Fog的pdf,它们很棒

另请参阅为何英特尔在这些年来改变了静态分支预测机制?:英特尔,因为据我们从性能实验中得知,试图对CPU的功能进行逆向工程,Sandybridge根本不使用静态预测。(许多较旧的CPU都将静态预测作为动态预测缺失时的后备。正常的静态预测是不采用前向分支,而采用后向分支(因为后向分支通常是循环分支)。)


效果likely()/unlikely()使用GNU C的宏__builtin_expect(如Drakosha的回答中提到)确实直接插入BP提示到ASM。(它可能可以通过进行操作gcc -march=pentium4,但在进行其他编译时则不能这样做)。

实际的效果是对代码进行布局,以便快速路径具有更少的分支,并且也许总指令更少。这将在静态预测起作用的情况下帮助分支预测(例如,动态预测变量很冷,在确实回落到静态预测的CPU上,而不仅仅是让分支在预测变量高速缓存中互为别名)。

请参见if else语句中GCC的__builtin_expect的优点是什么?有关代码生成的特定示例。

即使完美预测,采用分支的成本也比未采用分支的成本略高。当CPU提取16个字节的块中的代码以并行解码时,采用分支意味着该提取块中的后续指令不属于要执行的指令流。它在前端产生气泡,这可能成为高吞吐量代码的瓶颈(在高速缓存丢失时,后端不会停滞,并且具有高指令级并行性)。

在不同的块之间跳转还可能涉及更多的代码高速缓存行,从而增加了L1i高速缓存的占用空间,如果天气冷的话,可能会导致更多的指令高速缓存未命中。(以及潜在的uop-cache占用空间)。因此,这是快速路径短且线性的另一个优势。


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

否则,如果您未使用可能的/不太可能的宏并且未使用PGO,则GCC必须猜测使用了各种启发式方法。 -fguess-branch-probability默认情况下处于启用状态-O1

https://www.phoronix.com/scan.php?page=article&item=gcc-82-pgo&num=1拥有Xeon可扩展服务器CPU上PGO的基准测试结果与gcc8.2的常规结果。(Skylake-AVX512)。每个基准测试至少都有小幅提速,有的则受益于〜10%。(其中大部分可能是由于热循环中的循环展开,但其中某些原因可能来自更好的分支布局和其他效果。)


顺便说一句,如果您使用配置文件引导的优化,则可能不需要使用builtin_expect。PGO记录了每个分支的运行方式,因此,当您使用-fprofile-use进行编译时,gcc会知道哪种情况是每个分支的常见情况。不过,即使在没有PGO的情况下构建代码,也可以使用buildin_expect告诉它快速的路径。
彼得·科德斯

7

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

大多数处理器喜欢预取语句。通常,分支语句将在处理器内生成错误,导致其刷新预取队列。这是最大的惩罚。为了减少这种惩罚时间,请重写(和设计)代码,以减少可用的分支。同样,某些处理器可以有条件地执行指令而不必分支。

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


1
“布尔编程技术”是什么意思?
someonewithpc

@someonewithrpc通过使用按位运算将多个案例组合为一个案例。a(愚蠢但仍然)示例:替换a = b&1吗?0:1; 由a = b&1;
西蒙(Simon)

1

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

#pragma

如果条件表达式的一部分是函数调用或以函数调用开头,则此方法有效。

但是无法标记通用的if / while语句


-10

不,因为没有汇编命令可以让分支预测变量知道。不用担心,分支预测器非常聪明。

此外,关于过早优化及其弊端的强制性评论。

编辑:Drakosha提到了GCC的一些宏。但是,我认为这是代码优化,实际上与分支预测无关。


2
谢谢克努斯先生。如果这不是竞争的结果,他们的解决方案运行绝对最快,我完全同意。
安迪·舒尔曼

1
如果需要每个周期,为什么不只使用内联汇编呢?
rlbond

16
全文:“我们应该忘掉效率低下的情况,大约有97%的时间是这样:过早的优化是万恶之源。但是,我们不应该在那3%的临界水平上放弃自己的机会。 一个好的程序员不会被冷落通过这种推理使自己感到自满,明智的做法是,他仔细查看关键代码;但是只有在确定了该代码之后,” (重点是我的)

5
当分支预测变量对分支一无所知时,它具有静态规则:采用后向分支,不采用前向分支。如果您考虑一个for循环的工作原理,您将理解为什么这样做是有意义的,因为您跳回循环顶部的次数比不进行循环的次数多。因此,GCC宏控制的是GCC如何在内存中布置操作码,从而使前/后分支预测规则最有效。
唐·诺伊费尔德

1
这是完全错误的,实际上有一个汇编命令可让分支预测器知道。但是,除了Netburst之外,所有架构都忽略了它。
甘瑟·皮兹

-10

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

基本上,与其他许多富有成果的途径相比,这似乎极不可能实现任何重大改变。

编辑:感谢您的评论。我已经制作了这个社区Wiki,但是将其留在了这里,以便其他人可以看到评论。


1
没有可以使用的有效用例。例如,有些编译器以立即代码的形式输出到c,并在每行上放置“ if(break)break_into_debugger()”,以提供独立于平台的调试解决方案。
Lothar

8
实际上,在深度流水线处理器上,分支预测错误非常昂贵,因为它们需要完整的流水线刷新。合理的估计是指令执行成本的20倍。如果他的基准告诉他分支预测存在问题,那么他在做正确的事情。如果您没有尝试过,VTune可以为您提供很好的数据。
唐·诺伊费尔德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.