为了说清楚,我不打算在这里使用任何类型的便携性,所以任何将我绑定到某个盒子的解决方案都可以。
基本上,我有一个if语句将99%的时间评估为true,并且我试图剔除每个性能的最后一个时钟,我可以发出某种编译器命令(使用GCC 4.1.2和x86) ISA,如果有意义的话)告诉分支预测器它应该缓存该分支吗?
答案 0 :(得分:68)
是的,但它会有没有效果。例外是Netburst之前的旧(过时)架构,即便如此,它也没有做任何可测量的事情。
英特尔在Netburst架构中引入了一个“分支提示”操作码,以及一些旧架构上的冷跳(默认采取后向预测,前向预测未采取)的默认静态分支预测。 GCC使用__builtin_expect (x, prediction)
实现此功能,其中预测通常为0或1。
在所有较新的处理器架构(> = Core 2)上,编译器发出的操作码忽略。实际上做的事情的小角落案例就是旧Netburst架构上的冷跳。英特尔建议现在不要使用静态分支提示,可能是因为他们认为代码大小的增加比可能的边际加速更有害。
除了预测器的无用分支提示,__builtin_expect
有其用途,编译器可能会重新排序代码以提高缓存使用率或节省内存。
它有多种原因无法正常工作。
详细了解Agner Fogs manuals的分支预测的内部工作。 另请参阅gcc mailing list。
答案 1 :(得分:58)
是。 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.
答案 2 :(得分:29)
早期和以后的x86 CPU默默地忽略这些前缀字节。 Are there any performance test results for usage of likely/unlikely hints?提到PowerPC有一些跳转指令,它们具有分支预测提示作为编码的一部分。这是一个非常罕见的建筑特色。在编译时静态预测分支是很难准确的,因此通常最好将其留给硬件来解决。
关于最新的Intel和AMD CPU中的分支预测器和分支目标缓冲区的确切行为,正式发布的内容并不多。优化手册(在AMD和英特尔的网站上很容易找到)提供了一些建议,但没有记录特定的行为。有些人已经运行测试以试图实现实现,例如, Core2有多少BTB条目...无论如何,明确暗示预测器的想法已被放弃(暂时)。
所记录的是例如Core2具有分支历史缓冲区,如果循环总是以恒定的短迭代次数运行,则可以避免错误预测循环退出,< II或8 IIRC。但是不要太快展开,因为一个适合64字节(或Penryn上的19uops)的循环不会有指令获取瓶颈因为它从缓冲区重放...去阅读Agner Fog的pdf,它们是优异的
另请参阅Why did Intel change the static branch prediction mechanism over these years?:英特尔,因为Sandybridge根本不使用静态预测,只要我们从试图对CPU进行逆向工程的性能实验中分辨出来。 (当动态预测未命中时,许多较旧的CPU将静态预测作为后备。正常静态预测是不采用前向分支并且采用后向分支(因为向后分支通常是循环分支)。)
使用GNU C likely()
的{{1}} / unlikely()
宏的效果(如Drakosha的答案提及)确实不直接将BP提示插入到ASM 即可。 (它可能与__builtin_expect
一起使用,但在编译其他任何内容时都不行。)
实际效果是布置代码,以便快速路径占用较少的分支,并且可能总指令较少。这将有助于在静态预测发挥作用的情况下进行分支预测(例如,动态预测变量很冷,在CPU上可以回退到静态预测而不是仅仅让预测变量高速缓存中的分支别名。)
有关代码生成的具体示例,请参阅What is the advantage of GCC's __builtin_expect in if else statements?。
即使预测完美,分支机构的成本也略高于未被分支机构。当CPU以16字节的块的形式提取代码以并行解码时,采用的分支意味着该获取块中的后续指令不是要执行的指令流的一部分。它会在前端产生气泡,这可能成为高吞吐量代码的瓶颈(在高速缓存未命中时不会在后端停滞,并且具有高指令级并行性)。
在不同的块之间跳转也可能触及更多的代码缓存行,增加了L1i缓存占用空间,并且如果它很冷,可能会导致更多的指令缓存未命中。 (以及潜在的uop-cache足迹)。因此,快速路径的短路和线性是另一个优势。
GCC的配置文件引导优化通常会使可能/不可能的宏变得不必要。编译器收集运行时数据,以便每个分支以何种方式进行代码布局决策,并识别热块与冷块/功能。 (例如,它将在热门函数中展开循环,但不会展示冷函数。)请参阅gcc -march=pentium4
和-fprofile-generate
in the GCC manual。 How to use profile guided optimizations in g++?
否则GCC必须使用各种启发式猜测,如果你没有使用可能/不太可能的宏并且没有使用PGO。 -fprofile-use
及更高版本默认启用-fguess-branch-probability
。
https://www.phoronix.com/scan.php?page=article&item=gcc-82-pgo&num=1在Xeon可扩展服务器CPU上具有PGO与常规gcc8.2的基准测试结果。 (SKYLAKE微架构-AVX512)。每个基准都至少获得了一个小的加速,一些基准获益了~10%。 (其中大部分可能是在热循环中循环展开,但其中一些可能来自更好的分支布局和其他效果。)
答案 3 :(得分:6)
我建议而不是担心分支预测,分析代码并优化代码以减少分支数量。一个例子是循环展开,另一个例子是使用布尔编程技术而不是使用if
语句。
大多数处理器都喜欢预取语句。通常,分支语句将在处理器内生成 fault ,使其刷新预取队列。这是最大的惩罚。为了减少这个惩罚时间,重写(和设计)代码,以便减少可用的分支。此外,某些处理器可以有条件地执行指令而无需分支。
我通过使用循环展开和大I / O缓冲区将程序从1小时的执行时间优化到2分钟。在这种情况下,分支预测不会节省太多时间。
答案 4 :(得分:1)
SUN C Studio为此案例定义了一些编译指示。
#pragma rare_called()
如果条件表达式的一部分是函数调用或以函数调用开始,则此方法有效。
但是没有办法标记通用的if / while语句
答案 5 :(得分:-9)
不,因为没有汇编命令让分支预测器知道。不要担心,分支预测器非常聪明。
此外,关于过早优化及其如何恶化的强制性评论。
编辑:Drakosha为GCC提到了一些宏。但是,我认为这是一个代码优化,实际上与分支预测无关。答案 6 :(得分:-9)
这听起来像是矫枉过正 - 这种类型的优化会节省很少的时间。例如,使用更现代版本的gcc将对优化产生更大的影响。另外,尝试启用和禁用所有不同的优化标志;他们并没有提高绩效。
基本上,与许多其他富有成效的道路相比,这似乎不太可能产生任何显着差异。
编辑:感谢您的评论。我已经制作了这个社区wiki,但是留下了它,以便其他人可以看到评论。