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

Can I improve branch prediction with my code?

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

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

这样更好吗

if(true == get(gpioVal))
    unlikelyFunction();
else
    likelyFunction();

比这个?

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

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

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


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

  • Is it possible to tell the branch predictor how likely it is to follow the branch?

但未采用的直线代码比采用的成本更低,因此 提示高级语言以快速路径连续布局代码无助于分支预测的准确性,但可以帮助(或伤害)表现。 (I-缓存位置,前端带宽:记住代码获取发生在连续的 16 或 32 字节块中,因此采用的分支意味着该获取块的后面部分没有用。此外,分支预测 throughput; 某些 CPUs 例如 Intel Skylake 无法处理每 2 个时钟超过 1 个的预测分支,循环分支除外。这包括无条件分支,如 jmp 或 ret .)

采树枝难;未采用的分支使 CPU 保持警惕,但如果预测准确,它只是执行单元的正常指令(验证预测),对前端没有任何特殊之处。另见 现代微处理器 90 分钟指南! 其中有一节是关于分支预测的。 (而且总体来说优秀。)

  • How does the branch predictor know if it is not correct?

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

对于某些 CPU,尤其是较旧的分支布局有时确实会影响 运行时间预测:如果 CPU 没有记住它的动态预测器中关于分支的任何事情,标准的静态预测启发式是不采用前向条件分支,假定采用后向条件分支(因为那是 . See the BTFNT section in https://danluu.com/branch-prediction/.

编译器可以以任何一种方式布置 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.

的宏
  • https://en.cppreference.com/w/cpp/language/attributes/likely C++20 [[likely]]
  • 语法帮助
  • (对于字面的问题,不。对于实际想要的,对编译器的分支提示,是的。)
  • How do the likely/unlikely macros in the Linux kernel work and what is their benefit?使用__builtin_expect的GNU C宏,效果与C++20相同,语法不同[[likely]]
  • What is the advantage of GCC's __builtin_expect in if else statements? 示例 asm 输出。 (另请参阅 CiroSantilli 对其他一些问题的回答,他举例说明了这一点。)

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


缺少任何提示或配置文件数据

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

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

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