MESI 在 Intel 64 和 IA-32 上有什么意义

What is the point of MESI on Intel 64 and IA-32

编辑: 在进一步缩小真正让我困惑的范围之后,进行了实质性的编辑。我试图保持问题的一般概念不变,以保持所收到的重要答案的相关性。

回复:您的编辑,这似乎是一个新问题:对,存储转发 "violates" 一致性。一个核心可以比任何其他核心更早地看到自己的商店。存储缓冲区不连贯。

x86 内存排序规则要求加载按程序顺序全局可见,但允许内核在数据变为全局可见之前从其自己的存储中加载数据。它不必

也有关系; is a specific example of the store buffer + store-forwarding violating the usual memory-ordering rules. See this collection Linus Torvalds 的邮件列表帖子解释了存储转发对内存排序的影响(以及它如何意味着提议的锁定方案不起作用)。


如果完全没有一致性,您将如何以原子方式递增共享计数器,或实现其他对于实现锁或直接用于无锁代码必不可少的原子读-修改-写操作。 (参见 )。

lock add [shared_counter], 1 同时在多个线程中永远不会丢失实际 x86 上的任何计数,因为 lock 前缀使核心保持缓存行的独占所有权,从加载到存储提交到 L1d(因此变得全局可见)。

没有连贯缓存的系统会让每个线程递增自己的共享计数器副本,然后内存中的最终值将来自最后刷新该行的线程。

允许不同的缓存长期保存同一行的冲突数据,即使其他 loads/stores 发生,并且跨越内存障碍,将允许各种怪异。

这也违反了纯商店立即对其他核心可见的假设。如果您根本没有一致性,那么内核可以继续使用共享变量的缓存副本。所以 如果你想让 readers 注意到更新,你必须 clflush 在每次读取共享变量之前,这使得常见情况变得昂贵(当没有人修改数据时你上次检查过)。

MESI 就像一个推送通知系统,而不是强制每个 reader 在每次读取时重新验证其缓存。

MESI(或一般的一致性)允许 RCU(读取-复制-更新)之类的东西在 readers(与单线程相比)的情况下具有零开销共享数据结构尚未修改。参见 https://lwn.net/Articles/262464/, and https://en.wikipedia.org/wiki/Read-copy-update。基本思想是,编写器不是锁定数据结构,而是复制整个内容,修改副本,然后更新共享指针以指向新版本。所以 readers 总是完全不需要等待的;他们只是取消引用一个(原子)指针,数据在他们的 L1d 缓存中保持热。


硬件支持的一致性非常有价值,几乎每个共享内存 SMP 架构都使用它。即使是内存排序规则比 x86 弱得多的 ISA,如 PowerPC,也使用 MESI。

x86 上的 MESI 点与几乎任何多 core/CPU 系统上的点相同:强制缓存一致性。 x86 上等式的缓存一致性部分没有 "partial coherency":缓存是 完全一致的 。那么,可能的重新排序是连贯缓存系统和与核心本地组件(例如 load/store 子系统(尤其是存储缓冲区)和其他无序机制)交互的结果。

该交互的结果是 x86 提供的架构强大的内存模型,仅进行了有限的重新排序。没有一致的缓存,你根本无法合理地实现这个模型,或者几乎任何模型都不是完全弱1.

您的问题似乎嵌入了只有可能状态 "coherent" 和 "everything every else" 的假设。此外,缓存一致性(主要针对缓存,主要是隐藏的细节)和内存一致性模型这是架构定义的,每个架构都会实现2。维基百科 explains 缓存一致性和内存一致性之间的一个区别是前者的规则一次仅适用于一个位置,而一致性规则适用于多个位置。在实践中,更重要的区别是 内存一致性 模型是唯一有体系结构记录的模型。

简而言之,英特尔(和 AMD 同样)定义了特定的 内存一致性模型 x86-TSO3 - which is relatively strong as far as memory models go, but is still weaker than sequential consistency。与顺序一致性相比被削弱的主要行为是:

  • 后面的加载可以通过前面的存储。
  • 可以看到存储的顺序与总存储顺序不同,但只能由执行其中一个存储的核心看到。

为了实现这个内存模型,各个部分必须按照规则来实现它。在所有最近的 x86 上,这意味着有序的加载和存储缓冲区,这避免了不允许的重新排序。使用存储缓冲区会导致上面提到的两个重新排序:如果不允许这些,实现将非常受限并且可能会慢得多。在实践中,这也意味着完全一致的数据缓存,因为如果没有它,许多保证(例如,没有加载-加载重新排序)将很难实现。

总结一下:

  • 内存一致性与缓存一致性不同:前者是有文档记录的,并且构成了编程模型的一部分。
  • 在实践中,x86 实现具有 完全一致的缓存,这有助于他们实现 x86-TSO 内存模型,该模型相当强大但弱于顺序一致性。
  • 最后,也许是您正在寻找的答案,换句话说:弱于顺序一致性的内存模型仍然非常有用,因为您可以针对它进行编程,并且在某些特定操作需要顺序一致性的情况下( s) 你插入正确的内存屏障4.
  • 如果您针对语言提供的内存模型进行编程,例如 Java's or C++11's 您无需担心硬件细节,而不是语言内存模型,并且编译器会插入匹配所需的障碍语言记忆模型语义到硬件的语义。硬件模型越强大,所需的障碍就越少。

1 如果您的内存模型 完全弱,即没有真正对交叉核心重新排序施加任何限制,我想您可以以一种廉价的方式直接在非缓存一致性系统上实现它以进行正常操作,但是内存屏障可能会变得非常昂贵,因为它们需要刷新可能很大一部分本地私有缓存。

2 各种芯片可能在内部以不同的方式实现,特别是一些芯片可能实现比模型更强的语义(即,一些允许的重新排序永远不会被观察到),但是没有错误 none 将实现一个较弱的错误。

3这是那篇论文给它起的名字,我用这个名字是因为intel自己没有给它起名字,那篇论文是比较正规的定义英特尔给出了一个不太正式的模型作为一系列试金石测试。

4 在 x86 上练习时,您通常使用锁定指令(使用 lock 前缀)而不是单独的屏障,尽管也存在独立的屏障。这里我只使用术语 barries 来指代独立屏障和嵌入到锁定指令中的屏障语义。