为什么退休后的 RFO 不破坏内存排序?

Why doesn't RFO after retirement break memory ordering?

我以为我明白了L1D write miss是怎么处理的,但是仔细想想还是一头雾水。

这是一个汇编语言片段:

;rdi contains some valid 64-bytes aligned pointer
;rsi contains some data
mov [rdi], rsi
mov [rdi + 0x40], rsi        
mov [rdi + 0x20], rsi

假设 [rdi][rdi + 0x40] 行在 l1d 中不处于 Exclusive 或 Modified 状态。然后我可以想象以下操作序列:

  1. mov [rdi], rsi 退休。
  2. mov [rdi], rsi 尝试将数据写入 l1d。 RFO启动,数据放入WC缓冲区。
  3. mov [rdi + 0x40], rsi退休mov [rdi], rsi已经退休,所以有可能)
  4. mov [rdi + 0x40], rsi对连续的cache line发起RFO,数据放入WC buffer
  5. mov [rdi + 0x20], rsi退休mov [rdi + 0x40], rsi已经退休所以有可能)
  6. mov [rdi + 0x20], rsi 注意到 [rdi] 的 RFO 正在进行中。数据放入WC缓冲区。

  7. 砰! [rdi] RFO 恰好在 [rdi + 0x40] RFO 之前完成,因此 mov [rdi], rsimov [rdi + 0x20], rsi 的数据现在可以提交到缓存中。它破坏了内存排序。

如何处理这种情况以保持正确的内存排序?

启动 RFO 可以与将存储数据放入 LFB 分开;例如为尚未位于存储缓冲区头部的条目尽早启动 RFO 可以允许存储的内存级并行性。 您已经证明,要实现这一点,存储数据不能总是移入 LFB(行填充缓冲区,也用于 NT / WC 存储)。

如果 RFO 只能通过将存储数据从存储缓冲区 (SB) 移动到 LFB 来发生,那么是的,您只能对 SB 的头部进行 RFO,而不能并行地对任何分级条目进行 RFO。 ("graduated" 商店是其 uops 已从 ROB 中退出的商店,即变得非投机)。但是,如果您没有该要求,您 可以 RFO 甚至更早,甚至是推测性的,但您可能不想这样做。1

(考虑到@BeeOnRope 关于同一行的多个缓存未命中存储如何提交到一个 LFB,然后是另一行的另一个 LFB 的发现,这可能是让多个 RFO 处于飞行状态的机制,而不仅仅是SB 负责人。我们必须检查 ABA 存储模式是否限制了内存级并行性。如果是这样,那么启动 RFO is 可能与从 SB 移动数据相同到 LFB,释放该 SB 条目。 但请注意,在那些待处理的 RFO 完成并从 LFB 提交存储之前,SB 的新负责人仍然无法提交。)


一个非常接近现实的简单心理模型

在存储未命中时,存储缓冲区条目会保留存储数据,直到 RFO 完成,并直接提交到 L1d(将行从独占状态翻转为修改状态)。存储缓冲区头部的有序提交确保了强排序2.

正如@HadiBrais 在回答

时所写

My understanding is that for cacheable stores, only the RFO request is held in the LFB, but the data to be store waits in the store buffer until the target line is fetched into the LFB entry allocated for it. This is supported by the following statement from Section 2.4.5.2 of the Intel optimization manual:

The L1 DCache can maintain up to 64 load micro-ops from allocation until retirement. It can maintain up to 36 store operations from allocation until the store value is committed to the cache, or written to the line fill buffers (LFB) in the case of non-temporal stores.

这对于考虑性能调整来说非常好,但可能不是 MDS vulnerabilities 可以推测性地使用从 LFB 或其他任何地方读取错误负载的陈旧数据。

任何存储合并或其他技巧都必须遵循内存模型。


但是有那么简单吗?否

我们知道 CPUs 不能违反他们的内存模型,并且推测 + 回滚不是提交到全局可见状态(如 L1d)或一般分级存储的选项,因为uops 从 ROB 中消失了。就本地 OoO 执行官而言,它们已经发生了,这只是它们何时对其他核心可见的问题。我们还知道 LFB 本身 全局可见。 (有一些迹象表明 LFB 被来自该内核的负载窥探,例如存储缓冲区,但就 MESI 而言,它们更像是存储缓冲区的扩展。)

@BeeOnRope 做了一些更多的实验,发现了一些证据表明像 AAABBCCCC 这样的一系列商店可以排入三个 LFB,用于 A、B、C 行。RWT thread 实验证明了该理论预测的 4 倍性能差异。

这意味着 CPU 可以跟踪 LFB 之间的顺序,当然仍然不能 单个 LFB 内。像 AAABBCCCCA(或 ABA)这样的序列将无法提交到最后的 A 存储,因为 "current head" LFB 用于行 C,并且已经有一个 LFB 等待行 A 到达。第 4 行 (D) 可以,打开一个新的 LFB,但是添加到一个已经打开的 LFB 等待一个不是头的 RFO 是不行的。参见

所有这些仅针对英特尔 CPUs,AFAIK 进行了测试。


在此之前,我们认为 Intel/AMD 上没有存储合并,但长期以来一直对英特尔手册中有关 LFB 充当 WC 缓冲区以存储到正常(强有序)WB 内存的提示感到困惑

(本节未根据@BeeOnRope 的新发现进行更新)。

也没有确凿证据表明商店中有任何类型的商店合并/合并 现代 Intel 或 AMD CPUs 上的缓冲区,或者使用 WC 缓冲区(Intel 上的 LFB)在等待缓存行到达时保存存储数据。请参阅 Are two store buffer entries needed for split line/page stores on recent Intel? 下评论中的讨论。我们不能排除它在存储缓冲区的提交端附近的一些次要形式。

我们知道,特别是创建缓存ECC 颗粒的完整4 字节或8 字节写入以避免RMW 循环。但是 Intel CPUs 不会对高速缓存行中的狭窄或未对齐存储有任何惩罚。

有一段时间@BeeOnRope 和我认为有一些商店合并的证据,但我们改变了主意。 有更多详细信息(以及指向较早讨论的链接)。

(更新:现在终于有了存储合并的证据,以及对有意义的机制的解释。)


脚注 1: RFO 会消耗共享带宽并从其他核心窃取线路,从而减慢它们的速度。如果你 RFO 太早,你可能会在你真正投入之前再次失去这条线。加载也需要 LFB,您不想饿死它(因为在等待加载结果时执行会停止)。加载与存储有根本不同,通常是优先级。

因此,至少等待存储毕业是一个好计划,并且可能只为 head 之前的最后几个存储缓冲区条目启动 RFO。 (您需要在启动 RFO 之前检查 L1d 是否已经拥有该行,并且至少为标签占用缓存读取端口,尽管不是数据。我可能猜测存储缓冲区一次检查 1 个条目并标记一个条目可能不需要 RFO。)另请注意,1 个 SB 条目可能是未对齐的缓存拆分存储并触摸 2 个缓存行,最多需要 2 个 RFO...

脚注 2: 存储缓冲区条目按程序顺序分配(在缓冲区的尾部),因为指令/微指令被发布到乱序后端并为它们分配后端资源。 (例如,写入寄存器的 uops 的物理寄存器,可能会错误预测的条件分支 uops 的分支顺序缓冲区条目。)另请参见 。按顺序分配和提交保证商店的程序顺序可见性。存储缓冲区将全局可见的提交与存储地址和存储数据 uops(写入存储缓冲区条目)的乱序推测执行隔离开来,并且通常将执行与等待缓存未命中存储分离,直到存储缓冲区已满。

PS Intel 将存储缓冲区 + 加载缓冲区统称为内存顺序缓冲区 (MOB),因为它们需要了解彼此以跟踪推测性早期加载.这与您的问题无关,仅适用于推测性早期加载和检测内存顺序错误推测以及破坏管道的情况。

对于停用的存储指令(更具体地说是它们的 "graduated" 存储缓冲区条目),只是必须按程序顺序提交到 L1d 的存储缓冲区。