【问题标题】:Branch-mispredictions versus cache misses [closed]分支错误预测与缓存未命中 [关闭]
【发布时间】:2023-03-12 06:44:01
【问题描述】:

考虑以下两段代码:

备选方案 1:

if (variable != new_val) // (1)
    variable = new_val;

f(); // This function reads `variable`.

备选方案 2:

variable = new_val; // (2)
f(); // This function reads `variable`.

哪个选项“统计上”更快?假设variable 在 (1) 或 (2) 之前位于缓存 L1 中。

我猜即使分支误预测率很高,替代方案 (1) 也会更快,但我真的不知道“ifs”的成本。我的猜测是基于缓存未命中比分支错误预测更昂贵的假设,但我真的不知道。

如果variable 在 (1) 或 (2) 之前不在缓存中怎么办?是不是改变的太多了?

注意:由于不同 CPU 之间的情况可能会发生很大变化,因此您可以根据自己熟悉的架构来回答,尽管像任何现代 Intel 架构这样广泛使用的 CPU 是首选。我的问题的目标实际上是更多地了解 CPU 的工作原理。

【问题讨论】:

  • 没有基准测试就无法判断。
  • Alt 1 可以包括替代 2,作为乱序执行,在这种情况下,当谓词不成立时,结果将被丢弃。基于此,我会说备选方案 2 几乎总是更有效。即使使用微基准测试,也很难精确定位效率,因为您还必须考虑对程序其余部分的副作用,例如,仅预取的行为就会为预取器分配更多的工作量。另一点是,在进行比较时,您已经将变量放在寄存器中,这将是分配替代方案的重要组成部分
  • (1) 取决于 new_val 的先前值,如果需要,这将需要从缓存中获取它,而编译器可以完全忽略 (2) 中的先前值。如果 (1) 更快,我会感到惊讶,除非 variable 的类型有很大的 sizeof() 或者有一些产生副作用的赋值操作。但一如既往:不要假设,基准测试。
  • @Peregring-lk 错误预测的成本可能非常高。考虑管道冲洗。
  • 请记住,variable 可以放入寄存器,从而影响 变量 是否被缓存。据我了解,寄存器不涉及使用缓存,除了加载和存储值。因此f() 有可能不使用缓存,因为该值仍在寄存器中。取决于何时 variable 在f() 中使用以及编译器如何生成指令。

标签: c++ c performance cpu-architecture branch-prediction


【解决方案1】:

通常,备选方案 2 更快,因为它执行的机器代码更少,并且store buffer 会将无条件存储与内核的其他部分分离,即使它们在缓存中丢失。

如果备选方案 1 始终更快,编译器会生成这样做的 asm,但事实并非如此。它引入了可能的分支未命中和可能缓存未命中的负载。在某些看似合理的情况下它可能会更好(例如,与其他线程共享错误,或破坏数据依赖关系),但这些是您必须通过性能实验和性能计数器确认的特殊情况。


首先读取variable 已经触及两个变量的内存(如果两个变量都不在寄存器中)。如果您希望new_val 几乎总是相同(因此它预测得很好),并且对于缓存中未命中的负载,分支预测+推测执行可能有助于将以后对variable 的读取与缓存未命中负载分离.但它仍然是一个需要等待的缓存未命中负载,因为可以检查分支条件,因此如果分支预测错误,总未命中惩罚最终可能会非常大。但除此之外,您通过使更多的后续工作独立于它来隐藏很多缓存未命中负载损失,允许 OoO exec 高达the limit of the ROB size。

除了打破数据依赖外,如果f() inlines 和variable 优化到一个寄存器中,那么分支是没有意义的。否则,在 L1d 中未命中但在 L2 缓存中命中的存储仍然非常便宜,并且与存储缓冲区的执行脱钩。 (Can a speculatively executed CPU branch contain opcodes that access RAM?) 即使在 L3 中命中对于存储来说也不算太糟糕,除非其他线程将行置于共享状态并且弄脏它会干扰它们读取其他全局变量的值。 (虚假分享)

请注意,即使在存储等待从存储缓冲区提交到 L1d 缓存 (store forwarding) 时,以后重新加载 variable 也可以使用新存储的值,所以即使 f() 没有内联并且直接使用new_value 加载结果,它对variable 的使用仍然不必等待variable 上可能的存储丢失。


避免错误共享是值得进行分支以避免单个存储适合寄存器的值的少数原因之一。

@EOF 在 cmets 中链接的两个问题讨论了这种可能的优化(或可能的悲观化)以避免写入的情况。有时使用std::atomic 变量来完成,因为虚假共享是一个更大的问题。 (并且在除 AArch64 之外的大多数 ISA 上,使用默认 mo_seq_cst 内存顺序的存储速度很慢,耗尽了存储缓冲区。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-04-26
    • 2013-10-09
    • 2015-09-25
    • 2014-04-25
    • 2014-03-03
    • 2014-04-30
    • 2021-01-12
    • 2012-05-13
    相关资源
    最近更新 更多