barriers/fences 和获取、释放语义是如何在微架构上实现的?

how are barriers/fences and acquire, release semantics implemented microarchitecturally?

很多问题 SO 和 articles/books 例如 https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.2018.12.08a.pdf, Preshing's articles such as https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/ 和他的整个系列文章,抽象地谈论内存排序,根据不同障碍类型提供的排序和可见性保证。我的问题是这些障碍和内存排序语义是如何在 x86 和 ARM 微架构上实现的?

对于存储-存储障碍,似乎在 x86 上,存储缓冲区维护存储的程序顺序并将它们提交给 L1D(从而使它们以相同的顺序全局可见)。如果存储缓冲区未排序,即不按程序顺序维护它们,如何实现存储存储屏障?它只是 "marking" 存储缓冲区,在屏障提交之前存储到缓存相关域之前存储?还是内存屏障实际上刷新存储缓冲区并停止所有指令直到刷新完成?可以两种方式实现吗?

对于加载-加载障碍,如何防止加载-加载重新排序?很难相信 x86 会按顺序执行所有加载!我假设加载可以乱序执行,但 commit/retire 可以按顺序执行。如果是这样,如果 cpu 对 2 个不同的位置执行 2 次加载,一次加载如何确保它从 T100 中获取值,而下一次加载是在 T100 上或之后获取的?如果第一次加载未命中缓存并正在等待数据,而第二次加载命中并获取其值,该怎么办。当加载 1 获取其值时,它如何确保它获取的值不是来自加载 2 的值的较新商店?如果加载可以乱序执行,如何检测到违反内存顺序的情况?

同样,如何实现加载-存储障碍(隐含在 x86 的所有加载中)以及如何实现存储-加载障碍(例如 mfence)?即 dmb ld/st 和 dmb 指令在 ARM 上在微架构上做了什么,每次加载和存储以及 mfence 指令在 x86 上在微架构上做了什么以确保内存排序?

其他问答(尤其是后面的)已经介绍了其中的大部分内容,但我将在这里做一个总结。不过,问得好,将所有这些都收集在一个地方很有用。


在 x86 上,每个 asm 加载都是一个获取加载。为了有效地实现这一点,现代 x86 HW 推测加载时间早于允许的时间,然后检查该推测。 (可能导致内存顺序错误推测管道核弹。)为了跟踪这一点,英特尔将加载和存储缓冲区的组合称为“内存顺序缓冲区”。

弱顺序 ISA 不必推测,它们可以按任何顺序加载。


x86 存储排序 仅通过让存储按照程序顺序从存储缓冲区提交到 L1d 来维护。

至少在 Intel CPU 上,存储缓冲区条目在存储发布时 分配 (从前端到 ROB + RS)。所有 uops 都需要为它们分配一个 ROB 条目,但有些 uops 还需要分配其他资源,如加载或存储缓冲区条目、寄存器的 RAT 条目 read/write,等等。

所以我认为存储缓冲区本身有序的。当存储地址或存储数据 uop 执行时,它只是将地址或数据写入其已分配的存储缓冲区条目。由于提交(释放 SB 条目)和分配都是按程序顺序进行的,所以我假设它在物理上是一个有头有尾的循环缓冲区,就像 ROB 一样。 (与 RS 不同)。


避免 LoadStore 基本上是免费的:负载在执行之前不会退出(从缓存中获取数据)。商店在 退休之前无法提交。按顺序自动退役意味着所有先前的加载都在商店“毕业”并准备好提交之前完成。

实际上可以进行加载-存储重新排序的弱排序 uarch 可能会加载记分板并在 ROB 中跟踪它们:一旦已知它们没有故障,就让它们退休,但是,即使数据还没到。

这似乎更有可能出现在有序核心上,但 IDK。所以你可能有一个负载已经退休,但如果在数据实际到达之前有任何东西试图读取它,寄存器目标仍然会停止。我们知道有序内核在实践中确实以这种方式工作,不需要加载 complete 才能执行后面的指令。 (这就是为什么使用大量寄存器的软件流水线在此类内核上如此有价值,例如实现 memcpy。立即在有序内核上读取加载结果会破坏内存并行性。)

更深入地讨论了有序与乱序。


障碍说明

唯一对常规存储执行任何操作的屏障指令是 mfence,它实际上会停止内存操作(或整个流水线),直到存储缓冲区被耗尽。 Are loads and stores the only instructions that gets reordered? 还涵盖了 Skylake-with-updated-microcode 行为,表现得像 lfence

lfence 主要是为了阻止后续指令发出的微体系结构效果,直到所有先前的指令都离开乱序后端(退休)。 lfence 内存排序的用例几乎不存在。

相关:

  • How many memory barriers instructions does an x86 CPU have?
  • 详细介绍了 LFENCE 如何停止执行后面的指令,以及这对性能意味着什么。
  • When should I use _mm_sfence _mm_lfence and _mm_mfence 高级语言的内存模型比 x86 弱,因此您有时只需要一个编译为非 asm 指令的屏障。当您没有使用任何 NT 商店时使用 _mm_sfence() 只会让您的代码无缘无故地比 atomic_thread_fence(mo_release).