【问题标题】:Will a non-atomic load to the same cache line as an atomic variable cause the atomic variable to fail?将非原子加载到与原子变量相同的高速缓存行会导致原子变量失败吗?
【发布时间】:2018-05-20 18:35:23
【问题描述】:

鉴于 ARMv8 CPU 上的类似情况(尽管这可能也适用于许多其他人):

class abcxzy 
{
  // Pragma align to cacheline to ensure they exist on same line.
  unit32_t atomic_data;
  uint32_t data;

  void foo()
  {
    volatile asm (
      "   ldr w0, [address of data]\n"
      "# Do stuff with data in w0..."
      "   str w0, [address of data]\n"

      "1: ldaxr w0, [address of atomic_data]\n"
      "   add w1, w0, #0x1\n"
      "   stxr w2,w1, [address of atomic_data]\n"
      "   cbnz w2, 1b\n"
    );
  }
}

在 Asm inline 上设置适当的 clobbers 等,以便 C 和 Asm 可以在彩虹小马和阳光的世界中愉快地共存。

在多 CPU 的情况下,同时运行这段代码,存储到 data 是否会导致原子加载/存储到 atomic_data 失败?根据我的阅读,ARM atomic 的东西在缓存行的基础上工作,但不清楚非原子存储是否会影响原子。我希望它不会(并假设它会......),但我希望看看是否有其他人可以证实这一点。

【问题讨论】:

  • 原子和非原子操作都在缓存行的基础上工作。 C++ 内存模型允许同时修改不同地址的变量,即使它们在同一个缓存行中(这无论如何都很难说)。
  • @LWimsey 当使用asm 时,C++ 内存模型就消失了。例如,C++ 编译器可能会进行各种优化(例如,在一次 64 位访问中访问 atomic_datadata 或不使用“不必要的”原子指令)仍然导致它生成的代码符合 C++ 内存模型,但是在使用这个 asm 语句时会中断(或打破这个 asm 语句所做的假设)。
  • 老实说,更担心stxr 是否会在此处返回1,因为硬件检测到有人修改了原子变量的缓存行,而不是实际的原子变量,因此导致虚假故障。如果是这样,那么在原子行上有许多其他变量的情况下,它可能会导致原子由于缓存行干预而永远无法“完成”的情况,因此导致永久锁试图以原子方式递增变量.
  • 如果 C++ 编译器还生成独占访问指令,这些指令访问同一高速缓存行并同时在其他 CPU 上并行运行,那么肯定会出现误报问题。由于这种争用,您似乎不太可能遇到一个线程永远无法获得锁或其他任何情况的情况,但最终由您来确保这一点。
  • 您是在询问从不同 CPU 存储到data 是否会导致atomic_data 的原子RMW 失败,或者关于从data >相同的 CPU?在第一种情况下,答案是肯定的,另一个线程写入同一个缓存行绝对会导致 RMW 失败。

标签: c++ multithreading assembly arm atomic


【解决方案1】:

好的,终于找到我需要的了,虽然我不喜欢:

根据 ARM 文档,实现定义了非独占存储到与独占存储相同的缓存行是否会导致独占存储失败。谢谢阿姆。欣赏精彩的非结论性信息。


编辑:

失败是指stxr 命令没有写入内存并在状态寄存器中返回“1”。 “您的原子数据已更新,需要新的 RMW”状态。

回答其他陈述:

  • 是的,原子临界区应尽可能小。 docs 事件给出了关于多小的数字,它们确实非常合理。我希望我的部分永远不会超过 1k 或更多...

  • 是的,任何需要担心这种争用终止性能或更糟的情况都意味着您的代码“做错了”。 ARM 文档以一种全面的方式说明了这一点:)

  • 关于将非原子加载和存储放入原子内部 - 我上面的伪测试只是演示了对同一缓存行的随机访问作为示例。在实际代码中,您显然应该避免这种情况。我只是想感受一下,如果高速硬件计时器存储与锁定相同的缓存行可能会是多么“糟糕”。再说一次,不要这样做......

【讨论】:

  • 为了清楚起见,当他们说“失败”时,他们几乎肯定意味着stxr 需要重试。 (即它不会存储任何东西,并将设置 w2 非零,因此将采用 CBNZ)。它可能导致非原子存储对其他线程可见是不太合理的!尤其是当商店甚至不在ldaxrstxr(LL/SC 对)之间时
  • 您可以通过检测您的代码来测试您有权访问的 CPU:在 LL/SC 重试循环外增加一个计数器,在 LL/SC 循环内增加另一个(在寄存器中) .不同之处在于 LL/SC 重试次数。如果你得到的中断超出了单线程代码所能解释的范围,那么在某些情况下,将动态存储到同一缓存行是有问题的。
  • 当然ARM的立场是完全合理的。他们希望为各种当前和未来的实现保留他们的选择,即使他们确实想解释行为,也可能根本没有简单的规则。您应该假设并发存储到同一行可能会导致某些 LL/SC 操作失败,因为这些工作的一般模型是负载基本上在缓存行上设置一个标志“有一个挂起的 SC”,如果该行将被清除迷失在另一个核心。这就是为什么您要使关键区域尽可能小。
  • 现在一些实现可能,作为 QoS 问题,有一个策略,他们延迟放弃缓存线,其中 LL 设置为来自另一个内核的窥探,以给 SC 一个更好的成功机会,但即使这将是一个短暂的有界延迟。你认为即使 ARM 在某些芯片上这样做了,他们正在做的就是将其记录为架构行为?没门!甚至可能不是优化指南中的性能提示。这主要是学术性的,因为无论如何你都应该避免这种争论。
  • 如果您重新排序代码以将通用加载/存储放在 ldxr/stxr 测试中应该没问题。这将表示整个缓存行是原子提交的。此外,当您写入 CPU 缓存时,这会更快。另一种方式,CPU 必须在ldarx 之前刷新。如果这些值不相关,则不要将它们放在同一缓存行中(或始终在缓存颗粒中分配原子)。从这个角度来看,我不认为它们是限制的限制。
猜你喜欢
  • 1970-01-01
  • 2015-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-26
  • 2020-05-21
  • 2014-08-01
相关资源
最近更新 更多