为了清楚起见,我不打算在这里进行任何形式的可移植性,因此任何将我绑定到某个盒子的解决方案都可以。
基本上,我有一个 if 语句,它将 99% 的时间评估为真,并且我试图在最后一个时钟的性能上勉强,我可以发出某种编译器命令(使用 GCC 4.1.2 和 x86 ISA,如果重要)告诉分支预测器它应该为那个分支缓存?
为了清楚起见,我不打算在这里进行任何形式的可移植性,因此任何将我绑定到某个盒子的解决方案都可以。
基本上,我有一个 if 语句,它将 99% 的时间评估为真,并且我试图在最后一个时钟的性能上勉强,我可以发出某种编译器命令(使用 GCC 4.1.2 和 x86 ISA,如果重要)告诉分支预测器它应该为那个分支缓存?
是的,但它不会有任何影响。例外是 Netburst 之前的旧(过时)架构,即便如此,它也没有做任何可衡量的事情。
英特尔在 Netburst 架构中引入了一个“分支提示”操作码,并且在一些较旧的架构上为冷跳转(向后预测采用,前向预测未采用)提供了默认静态分支预测。GCC 使用 来实现这一点__builtin_expect (x, prediction)
,其中预测通常为 0 或 1。编译器发出的操作码在所有较新的处理器架构 (>= Core 2) 上都会被忽略。这实际上做某事的小角落案例是旧的 Netburst 架构上的冷跳案例。英特尔现在建议不要使用静态分支提示,可能是因为他们认为代码大小的增加比可能的边际加速更有害。
除了预测器的无用分支提示__builtin_expect
有其用途外,编译器可能会重新排序代码以提高缓存使用率或节省内存。
它不能按预期工作有多种原因。
在 Agner Fogs手册中阅读有关分支预测内部工作的更多信息。另请参阅 gcc邮件列表。
是的。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.
Pentium 4(又名 Netburst 微架构)有分支预测器提示作为 jcc 指令的前缀,但只有 P4 对它们做过任何事情。请参阅http://ref.x86asm.net/geek32.html。以及 Agner Fog 的优秀 asm opt 指南的第 3.5 节,来自 http://www.agner.org/optimize/。他也有 C++ 优化指南。
早期和后来的 x86 CPU 会默默地忽略这些前缀字节。 是否有任何使用可能/不太可能提示的性能测试结果?提到 PowerPC 有一些跳转指令,其中有一个分支预测提示作为编码的一部分。这是一个非常罕见的建筑特征。在编译时静态预测分支很难准确地完成,所以通常最好让硬件来解决它。
关于最新的 Intel 和 AMD CPU 中的分支预测器和分支目标缓冲区的具体行为方式,官方并未公布太多信息。优化手册(在 AMD 和 Intel 的网站上很容易找到)提供了一些建议,但没有记录具体行为。有些人已经运行测试来试图预测实现,例如Core2有多少BTB条目......无论如何,明确暗示预测器的想法已经被放弃(现在)。
例如,记录的内容是 Core2 有一个分支历史缓冲区,如果循环始终运行恒定的短迭代次数(< 8 或 16 IIRC),则可以避免错误预测循环退出。但是不要太快展开,因为适合 64 字节(或 Penryn 上的 19 uop)的循环不会有指令获取瓶颈,因为它是从缓冲区重放的……去阅读 Agner Fog 的 pdf,它们非常棒。
另请参阅为什么这些年来英特尔改变了静态分支预测机制?:英特尔,因为 Sandybridge 根本不使用静态预测,据我们从试图对 CPU 的功能进行逆向工程的性能实验中得知。(许多较旧的 CPU 在动态预测未命中时将静态预测作为后备。正常的静态预测是不采用前向分支而采用后向分支(因为后向分支通常是循环分支)。)
使用 GNU C 的likely()
/宏的效果(就像 Drakosha 的回答提到的那样)不会直接将 BP 提示插入到 asmunlikely()
__builtin_expect
中。(它可能会这样做gcc -march=pentium4
,但不是在编译其他任何东西时)。
实际效果是对代码进行布局,以便快速路径具有更少的分支,并且可能总共更少的指令。这将有助于在静态预测发挥作用的情况下进行分支预测(例如,动态预测器是冷的,在确实回退到静态预测的 CPU 上,而不是让分支在预测器缓存中相互别名。)
请参阅GCC 的 __builtin_expect 在 if else 语句中的优势是什么?有关代码生成的具体示例。
即使在完美预测的情况下,采用分支的成本也略高于未采用的分支。当 CPU 以 16 字节的块获取代码以并行解码时,采用的分支意味着该获取块中的后续指令不是要执行的指令流的一部分。它在前端产生气泡,这可能成为高吞吐量代码的瓶颈(在缓存未命中时不会在后端停止,并且具有高指令级并行性)。
在不同的块之间跳转也可能会触及更多的代码缓存行,增加 L1i 缓存占用空间,如果它很冷,可能会导致更多的指令缓存未命中。(以及潜在的 uop-cache 占用空间)。所以这是让快速路径短而线性的另一个优势。
GCC 的配置文件引导优化通常使可能/不太可能的宏变得不必要。编译器收集每个分支用于代码布局决策的运行时数据,并识别热块/冷块/函数。(例如,它会在热函数中展开循环,但不会在冷函数中展开循环。)参见GCC 手册中的-fprofile-generate
和。 如何在 g++ 中使用配置文件引导优化?-fprofile-use
否则,如果您没有使用可能/不太可能的宏并且没有使用 PGO,GCC 必须使用各种启发式方法进行猜测。 -fguess-branch-probability
默认情况下启用 at-O1
及更高版本。
https://www.phoronix.com/scan.php?page=article&item=gcc-82-pgo&num=1在 Xeon Scalable Server CPU 上提供了 PGO 与常规 gcc8.2 的基准测试结果。(Skylake-AVX512)。每个基准测试都至少有小幅加速,有些则受益约 10%。(其中大部分可能来自热循环中的循环展开,但其中一些可能来自更好的分支布局和其他效果。)
我建议不要担心分支预测,而是分析代码并优化代码以减少分支数量。一个例子是循环展开,另一个例子是使用布尔编程技术而不是使用if
语句。
大多数处理器喜欢预取语句。通常,分支语句将在处理器内产生故障,导致它刷新预取队列。这是最大的惩罚。为了减少这种惩罚时间,重写(和设计)代码以减少可用的分支。此外,一些处理器可以有条件地执行指令而无需分支。
通过使用循环展开和大型 I/O 缓冲区,我将程序的执行时间从 1 小时优化到了 2 分钟。在这种情况下,分支预测不会节省太多时间。
SUN C Studio 为这种情况定义了一些 pragma。
#pragma 很少调用 ()
如果条件表达式的一部分是函数调用或以函数调用开头,则此方法有效。
但是没有办法标记一个通用的 if/while 语句
不,因为没有汇编命令让分支预测器知道。别担心,分支预测器非常聪明。
此外,关于过早优化及其邪恶的强制性评论。
编辑: Drakosha 提到了 GCC 的一些宏。但是,我认为这是代码优化,实际上与分支预测无关。
这对我来说听起来有点矫枉过正——这种优化将节省少量时间。例如,使用更现代的 gcc 版本将对优化产生更大的影响。另外,尝试启用和禁用所有不同的优化标志;它们并不能提高性能。
基本上,与许多其他富有成效的路径相比,这似乎不太可能产生任何重大差异。
编辑:感谢您的评论。我制作了这个社区 wiki,但将其保留在其中,以便其他人可以看到评论。