【问题标题】:Why cannot the load part of the atomic RMW instruction pass the earlier store to unrelated location in TSO(x86) memory consistency model?为什么原子 RMW 指令的加载部分不能将较早的存储传递到 TSO(x86) 内存一致性模型中的不相关位置?
【发布时间】:2017-08-06 19:17:05
【问题描述】:

众所周知,x86 架构由于使用了写缓冲区而没有实现顺序一致性内存模型,因此可以发生存储->加载重新排序(可以提交稍后的加载,而早期的存储仍然驻留在写缓冲区中等待提交到 L1 缓存)。

A Primer on Memory Consistency and Coherence 中,我们可以了解 Total Store Order(TSO) 内存一致性模型(应该与 x86 非常相似)中的 Read-Modify-Write(RMW) 操作:

...我们认为 RMW 作为负载,紧随其后的是存储。负载部分 由于 TSO 的订购规则,RMW 无法通过较早的负载。它 乍一看,RMW 的负载部分可能 在写入缓冲区中传递较早的存储,但这是不合法的。如果 RMW 的加载部分通过较早的存储,然后是存储 部分 RMW 也必须通过较早的商店 因为RMW是一个原子对。但是因为商店没有 允许在TSO中互相通过,RMW的负载部分不能 也可以通过较早的商店。

好的,原子操作必须是原子的,即RMW访问的内存位置在RMW操作期间不能被其他线程/内核访问,但是,如果较早的存储通过原子操作的加载部分不是与RMW访问的内存位置有关?假设我们有以下几条指令(伪代码):

store int32 value in 0x00000000 location
atomic increment int32 value in 0x10000000 location

第一个存储被添加到写入缓冲区并等待轮到它。同时,原子操作从另一个位置(甚至在另一个缓存行)加载值,传递第一个存储,并在第一个存储之后将存储添加到写入缓冲区中。在全局内存顺序中,我们将看到以下顺序:

加载(原子的一部分)-> 存储(序数)-> 存储(原子的一部分)

是的,从性能的角度来看,这可能不是最佳解决方案,因为我们需要将原子操作的缓存线保持在读写状态,直到写入缓冲区中的所有先前存储都被提交,但是,除了性能考虑之外,是否存在违反 TSO 内存一致性模型的情况,我们是否允许 RMW 操作的加载部分将较早的存储传递到不相关的位置

【问题讨论】:

  • 如果您使用指令对(加载链接存储条件)来实现原子增量操作,我看不出您建议的顺序有什么问题。但是,如果是单条指令,那是不可能的,因为 atomic 的加载部分变成了微操作,我们正在尝试混合操作和微操作,可能不是一个好主意。
  • @IsuruH 在 x86 上它是一条指令。但是这种混合有什么问题呢?微加载操作不等待之前的存储并从缓存中获取值,而微存储操作只是将结果放入写入缓冲区。
  • @IsuruH 在 x86 上,RMW 操作使用 lock 前缀实现,除其他外,它可以在原子指令执行期间将高速缓存行保持在 M 状态。一旦指令退出,锁就会被释放,所以,是的,将 RMW 操作的存储部分放在写缓冲区中可能会违反操作的原子性,因为从存储被放置到它被写入缓存的时间其他核心可以访问旧值。所以它特别给出了我的问题的答案,尽管它是一个实现细节而不是 TSO 的概念限制。
  • 谢谢!!您的评论和@Leeor 回答解释了为什么不能这样做。但是在我看来,从技术上讲,您可以允许在原子操作的读取和写入之间耗尽存储到不同的缓存行。我对微操作的了解有限,所以我不确定您将如何重新排序指令的某些部分,对我来说,重新排序发生在指令级别。
  • @IsuruH AFAIK,即使没有 CPU 对指令进行实际重新排序,这种“重新排序”也可能发生。即使您有一个带有单个管道和按顺序提交的标量 CPU,您只需要立即从缓存或写入缓冲区加载值(如果它包含到所需位置的最近存储),但是将存储推送到写入缓冲区,从而延迟他们。在这种情况下,即使是微操作,Store->Load memory 操作的全局顺序也会改变。

标签: x86 atomic cpu-architecture memory-barriers memory-model


【解决方案1】:

您可以针对不同地址的任何存储 + 加载对提出相同的问题:由于无序执行,加载可能比旧存储在内部执行得更早。在 X86 中这是允许的,因为:

可以将旧商店重新订购到不同位置,但不能将旧商店重新订购到同一位置

(来源:Intel 64 Architecture Memory Ordering White Paper

但是,在您的示例中,锁定前缀会阻止这种情况,因为(来自同一组规则):

锁定指令有总顺序

这意味着锁将强制执行内存屏障,例如 mfence(确实有些编译器使用锁定操作作为屏障)。这通常会使 CPU 停止执行加载,直到存储缓冲区耗尽,从而强制存储首先执行。

【讨论】:

  • 感谢您的链接。从我在“A Primer ...”中读到的内容,我错误地得出结论,x86 上 RMW 操作的原子性(由于使用 lock)以及因此无法使用早期存储重新排序部分负载是由某些人决定的概念规律。但是好像情况正好相反,lock的用法和缓冲区排水都是实现细节(可能是性能考虑引起的),这也是为什么部分原子操作不能重排的原因。跨度>
  • 您需要区分RMW内部原子性,这意味着没有人可以在加载和存储之间抢占内存位置,(即 - 两个并行的“锁定inc”操作将不得不增加内存值加 2,而未锁定的操作在理论上只能加 1(如果它们重叠),以及内存排序。第一个是用于帮助 MT 编程的构造,后者是一组规则,用于解释单个上下文中可能和不可能的可见订单。
【解决方案2】:

因为我们需要为原子操作保存缓存行 读写状态,直到写入缓冲区中的所有先前存储都 已承诺,但不考虑性能因素

如果您在执行与 L 阻止的操作具有相同性质的操作 S 时持有锁 L,则存在可以被 L 阻塞(延迟)的 S',并且 S 可以被 L 阻塞(延迟) ',那么你就有了陷入僵局的秘诀,除非你保证是唯一这样做的演员(这会使整个原子的事情变得毫无意义)。

【讨论】:

    猜你喜欢
    • 2021-12-23
    • 2021-11-05
    • 1970-01-01
    • 2012-04-23
    • 1970-01-01
    • 2015-05-26
    • 1970-01-01
    • 2020-05-23
    • 1970-01-01
    相关资源
    最近更新 更多