我可以用我的代码改进分支预测吗?

slo*_*ile 3 c++ optimization gcc branch-prediction likely-unlikely

这是一个对任何平台、语言或编译器都开放的天真的一般性问题。虽然我最好奇的是 Aarch64、C++、GCC。

当在依赖于 I/O 状态的程序流中编写不可避免的分支时(编译器无法预测),并且我知道一种状态比另一种状态更有可能,我如何向编译器表明这一点?

这是否更好

if(true == get(gpioVal))
    unlikelyFunction();
else
    likelyFunction();
Run Code Online (Sandbox Code Playgroud)

比这个?

if(true == get(gpioVal))
    likelyFunction(); // performance critical, fill prefetch caches from this branch
else
    unlikelyFunction(); // missed prediction not consequential on this branch
Run Code Online (Sandbox Code Playgroud)

如果通信协议使更有可能或临界值为真(高)或假(低),这是否有帮助?

Pet*_*des 13

TL:DR: 是的,在 C 或 C++ 中使用likely()宏或 C++20[[likely]]来帮助编译器制作更好的 asm。不过,这与影响实际的 CPU 分支预测是分开的。如果用 asm 编写,请布局代码以尽量减少所采用的分支。


对于大多数 ISA,asm 中无法提示 CPU 是否可能采用分支。(一些例外包括 Pentium 4(但不包括更早或更高版本的 x86)、PowerPC 和一些 MIPS,它们允许分支提示作为条件分支汇编指令的一部分。)

但未采用的直线代码比采用的直线代码更便宜,因此暗示高级语言以连续的快速路径布局代码无助于分支预测的准确性,但可以帮助(或损害)性能。(I-cache 局部性、前端带宽:记住代码获取发生在连续的 16 或 32 字节块中,因此采用的分支意味着该获取块的后面部分没有用。此外,分支预测吞吐量;某些CPU例如,像 Intel Skylake 一样,除了循环分支之外,无法以超过每 2 个时钟 1 个的速度处理预测采用的分支。其中包括 jmp 或 ret 等无条件分支。)

摘下来的树枝是坚硬的;未采取的分支使 CPU 保持警惕,但如果预测准确,它只是执行单元的正常指令(验证预测),对于前端来说没有什么特别的。另请参阅现代微处理器 90 分钟指南!其中有一个关于分支预测的部分。(总体来说非常出色。)


许多人将源级分支提示误解为分支预测提示。如果针对支持汇编中分支提示的 CPU 进行编译,这可能是一种影响,但对于大多数情况来说,最重要的影响在于布局,并决定是否使用无分支 ( cmov) ;条件[[likely]]也意味着它应该能够很好地预测。

对于某些 CPU,尤其是较旧的 CPU,分支的布局有时确实会影响运行时预测:如果 CPU 在其动态预测器中不记得有关分支的任何信息,则标准静态预测启发式是前向条件分支不被采用,后向条件分支不被采用假设采用(因为这通常是循环的底部。请参阅https://danluu.com/branch-prediction/中的 BTFNT 部分。

编译器可以采用if(c) x else y;任何一种方式进行布局,要么将源代码与jump over x if !c作为开头的内容进行匹配,要么交换 if 和 else 块并使用相反的分支条件。或者它可以将一个块置于行外(例如,在ret函数末尾之后),因此快速路径不会有条件或以其他方式采取分支,而不太可能的路径必须跳转到那里然后跳回来。


在高级源代码中使用分支提示很容易弊大于利,特别是如果周围的代码发生变化而不注意它们,因此配置文件引导优化是编译器了解分支可预测性和可能性的最佳方法。(例如gcc -O3 -fprofile-generate/ 使用一些代表性输入运行,以相关方式执行代码路径 / gcc -O3 -fprofile-use

但在某些语言中,有一些方法可以进行提示,例如 C++20[[likely]]和,它们是 GNU C /宏[[unlikely]]的可移植版本。likely()unlikely()__builtin_expect

我不知道为 GNU C / C++ 和 ISO C++20 以外的语言注释分支的方法。


没有任何提示或个人资料数据

如果没有这一点,优化编译器必须使用启发式方法来猜测分支的哪一侧更有可能。如果它是循环分支,他们通常假设循环将运行多次。在 上if,他们有一些基于实际情况的启发式方法,也许是受控制的块中的内容;IDK 我还没有研究过 gcc 或 clang 做什么。

不过,我注意到 GCC 确实关心这种情况。它并不像假设int值是均匀随机分布那样天真,尽管我认为它通常假设这if (x == 10) foo();有点不太可能。

像 JVM 中的 JIT 编译器在这里有一个优势:它们可以在运行的早期阶段检测分支,在进行最终优化的汇编之前收集分支方向信息。OTOH 他们需要快速编译,因为编译时间是总运行时间的一部分,所以他们不会努力制作好的 asm,这在代码质量方面是一个主要缺点。


归档时间:

查看次数:

2483 次

最近记录:

4 年,5 月 前