为了清楚起见,我在这里不打算进行任何形式的可移植性,因此任何将我与特定机器绑定在一起的解决方案都可以。
基本上,我有一条if语句,它将在99%的时间中将其评估为true,并试图提高性能的最后一个时钟,我是否可以发出某种编译器命令(使用GCC 4.1.2和x86 ISA,如果重要)告诉分支预测器它应该为该分支缓存?
为了清楚起见,我在这里不打算进行任何形式的可移植性,因此任何将我与特定机器绑定在一起的解决方案都可以。
基本上,我有一条if语句,它将在99%的时间中将其评估为true,并试图提高性能的最后一个时钟,我是否可以发出某种编译器命令(使用GCC 4.1.2和x86 ISA,如果重要)告诉分支预测器它应该为该分支缓存?
Answers:
是。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.
是的,但是没有效果。例外是Netburst之前的较旧(过时)体系结构,即使如此,它也无法做任何可衡量的事情。
英特尔在Netburst架构中引入了一个“分支提示”操作码,在某些较旧的架构上,默认的静态分支预测用于冷跳跃(向后预测采用,向前预测未采用)。GCC使用来实现此目标__builtin_expect (x, prediction)
,其中预测通常为0或1。所有较新的处理器体系结构(> = Core 2)都会忽略编译器发出的操作码。实际起作用的小角落情况是旧的Netburst架构发生冷跳的情况。英特尔现在建议不要使用静态分支提示,这可能是因为他们认为增加代码大小比提高边际速度有害得多。
除了对预测变量的无用分支提示__builtin_expect
有用之外,编译器还可以对代码重新排序以提高缓存使用率或节省内存。
有多种原因导致其无法正常运行。
奔腾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%。(其中大部分可能是由于热循环中的循环展开,但其中某些原因可能来自更好的分支布局和其他效果。)
我建议不要担心分支预测,而对代码进行概要分析并优化代码以减少分支数量。一个示例是循环展开,另一个示例是使用布尔编程技术而不是if
语句。
大多数处理器喜欢预取语句。通常,分支语句将在处理器内生成错误,导致其刷新预取队列。这是最大的惩罚。为了减少这种惩罚时间,请重写(和设计)代码,以减少可用的分支。同样,某些处理器可以有条件地执行指令而不必分支。
通过使用循环展开和大型I / O缓冲区,我从1小时的执行时间到2分钟的时间优化了程序。在这种情况下,分支预测不会节省很多时间。
不,因为没有汇编命令可以让分支预测变量知道。不用担心,分支预测器非常聪明。
此外,关于过早优化及其弊端的强制性评论。
编辑:Drakosha提到了GCC的一些宏。但是,我认为这是代码优化,实际上与分支预测无关。
在我看来,这听起来像是过大杀伤力-这种类型的优化将节省少量时间。例如,使用更现代的gcc版本将对优化产生更大的影响。另外,尝试启用和禁用所有不同的优化标志;它们并不能提高性能。
基本上,与其他许多富有成果的途径相比,这似乎极不可能实现任何重大改变。
编辑:感谢您的评论。我已经制作了这个社区Wiki,但是将其留在了这里,以便其他人可以看到评论。