TL:DR:是的,在 C 或 C++ 中使用likely()
宏或 C++20[[likely]]
来帮助编译器生成更好的 asm。不过,这与影响实际 CPU 分支预测是分开的。如果用 asm 编写,请布置代码以尽量减少占用的分支。
对于大多数 ISA,asm 无法提示 CPU 是否可能采用分支。(一些例外包括 Pentium 4(但不是更早或更晚的 x86)、PowerPC 和一些 MIPS,它们允许将分支提示作为条件分支 asm 指令的一部分。)
但是未采用的直线代码比采用的要便宜,因此提示高级语言以快速路径连续布局代码无助于分支预测的准确性,但可以帮助(或损害)性能。(I-cache 局部性,前端带宽:记住代码提取发生在连续的 16 或 32 字节块中,因此采用的分支意味着该提取块的后面部分没有用。此外,分支预测吞吐量;一些 CPU例如,像 Intel Skylake 一样,除了循环分支之外,不能以每 2 个时钟超过 1 个的速度处理预测的分支。这包括像 jmp 或 ret 这样的无条件分支。)
拿的树枝是硬的;未采用的分支使 CPU 保持警觉,但如果预测准确,它只是执行单元的正常指令(验证预测),前端没有什么特别的。另请参阅现代微处理器 90 分钟指南!其中有一个关于分支预测的部分。(而且总体来说很棒。)
许多人将源级分支提示误解为分支预测提示。如果为支持 asm 中的分支提示的 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]]
和[[unlikely]]
,它们是 GNU C likely()
/unlikely()
宏的可移植版本__builtin_expect
。
我不知道为 GNU C / C++ 和 ISO C++20 以外的语言注释分支的方法。
没有任何提示或个人资料数据
否则,优化编译器必须使用启发式方法来猜测分支的哪一侧更有可能。如果它是一个循环分支,他们通常假设循环将运行多次。在 an 上if
,他们有一些基于实际情况的启发式方法,也许是被控制的块中的内容;IDK 我还没有研究 gcc 或 clang 做什么。
不过,我注意到 GCC 确实关心这种情况。它不像假设int
值是均匀随机分布的那样天真,尽管我认为它通常假设这if (x == 10) foo();
不太可能。
像 JVM 中的 JIT 编译器在这里有一个优势:它们可以潜在地在运行的早期阶段检测分支,在制作最终优化的 asm 之前收集分支方向信息。OTOH 他们需要快速编译,因为编译时间是总运行时间的一部分,所以他们不会努力制作好的 asm,这在代码质量方面是一个主要缺点。