Intel X86如何实现全店排序

pve*_*jer 1 x86 intel cpu-architecture memory-barriers micro-architecture

由于其 TSO 内存模型,X86 保证所有商店的总顺序。我的问题是是否有人知道这是如何实际实施的。

我对所有 4 个围栏是如何实现的印象很好,所以我可以解释如何保留本地秩序。但是 4 个栅栏只会给 PO;它不会给您 TSO(我知道 TSO 允许旧商店跳到新负载前面,因此只需要 4 个围栏中的 3 个)。

单个地址上所有内存操作的总顺序是一致性的责任。但我想知道英特尔(特别是 Skylake)如何在多个地址的商店上实现总订单。

Pet*_*des 5

x86 TSO 内存模型基本上相当于程序顺序加上带有存储转发的存储缓冲区。

大多数由此产生的保证在理论上通过简单的存储缓冲区和一致的共享内存对于硬件来说是相当容易实现的;存储缓冲区将 OoO exec 与有序提交要求(以及缓存未命中存储)隔离,并使推测性地执行存储和重新加载成为可能。

  • 所有核心都可以就所有存储发生的总顺序达成一致。或者更准确地说,内核不能对它们实际观察到的总顺序的任何部分产生分歧。同时存储到 2 个不同的行是同时的,因此任何观察都与假设的总顺序中的任一顺序兼容。

    如果使存储对任何其他内核可见的唯一方法使其同时对所有内核可见,则会自动发生这种情况。即通过致力于连贯的 L1d。这使得 IRIW 无法重新排序。(MESI 确保商店不能提交到 L1d,除非它由该核心独家拥有:没有其他核心拥有有效副本。)(观察自己商店的核心需要一个完整的屏障,否则它将通过商店转发观察自己的商店,而不是全局总顺序。典型的 IRIW 试金石测试考虑 4 个总线程,因此没有本地重新加载。)

    事实上,这是罕见的任何硬件具有这种属性; 一些POWER CPU可以在同一物理核心上的 SMT 线程之间进行存储转发,这使得 2 个读者可能对 2 个作者的存储顺序产生分歧(IRIW 重新排序)。尽管 x86 CPU 通常也有 SMT(例如 Intel 的超线程),但内存模型要求它们不能在逻辑内核之间进行存储转发。没关系; 无论如何,它们都会对存储缓冲区进行静态分区。 使用 HT 在一个 Core 上执行的线程之间的数据交换将使用什么?. 以及生产者-消费者在超级兄弟与非超级兄弟之间共享内存位置的延迟和吞吐量成本是多少? 用于实验测试。

唯一发生的重新排序是本地的,在每个 CPU 内核中,在它访问全局一致的共享状态之间。 (这就是为什么本地内存屏障只是让这个核心等待事情发生,例如存储缓冲区耗尽,可以在 x86 TSO 之上恢复顺序一致性。这同样适用于较弱的内存模型,顺便说一句:只是本地重新排序MESI 一致性的顶部。)

这些保证的其余部分分别适用于每个(逻辑)CPU 内核。(关于这如何在内核之间创建同步的问答。)

  • 存储按程序顺序变得可见:从存储缓冲区到 L1d 缓存的有序提交。(存储缓冲区条目在发布/重命名期间按程序顺序分配)。这意味着缓存未命中存储必须停止存储缓冲区,而不是让新存储提交。请参阅为什么退休后 RFO 不中断内存排序?有关此问题的简单心理模型,以及有关 Skylake 可能实际执行的操作的一些详细信息(在等待缓存行到达时将存储未命中的数据提交到 LFB)。

  • 加载不会与以后的存储重新排序:简单:需要加载完全完成(已从 L1d 缓存中获取数据)才能退休。由于退休是有序的,并且商店它退休之后才能承诺 L1d (变得非投机性),我们可以免费订购 LoadStore 1

  • 加载按程序顺序从一致缓存(内存)中获取数据。这是难点:加载在执行时访问全局状态(缓存),这与存储缓冲区可以吸收 OoO exec 和有序提交之间的不匹配的存储不同。实际上,让每个负载都依赖于先前的负载,可以防止未命中并扼杀涉及内存的代码乱序执行的许多好处。

    在实践中,英特尔 CPU 积极地推测,当架构上允许加载发生时(在执行较早的加载之后),现在存在的缓存行仍将存在。如果不是这种情况,请取消管道(内存顺序错误推测)。为此有一个性能计数器事件。

在实践中,为了追求更高的性能,或者对于推测性的早期加载,一切都可能更加复杂。

(在 C++ 术语中,这至少与 一样强acq_rel,但也涵盖了在 C++ 中可能是 UB 的事物的行为。例如,加载部分重叠最近的存储到另一个线程也可能正在读取或写入的位置,允许这样做core 加载一个从未出现或将出现在内存中的值供其他线程加载。 全局不可见加载指令

相关问答:


脚注 1:
一些 OoO exec 弱排序 CPU 可以执行 LoadStore重新排序,大概是通过让加载从 ROB 退出,只要加载检查权限并请求缓存行(对于未命中),即使数据实际上没有还没到。需要对未准备好的寄存器进行一些单独的跟踪,而不是通常的指令调度程序。

LoadStore 重新排序实际上在有序管道上更容易理解,我们知道需要对缓存未命中加载进行特殊处理才能获得可接受的性能。 如何通过有序提交实现加载-> 存储重新排序?

  • @pveentjer:请注意,与单一位置的情况不同,您可以同时将商店存储到不同的位置。您不需要序列化,您只需要确保没有线程可以*不同意*。不需要任何真实的或假设的读者能够看到一个商店发生而另一个商店没有发生的状态。让两个写入器核心在同一时钟周期内提交(假设跨核心的时钟同步...)意味着这些行的任何共享请求顺序都不会看到存储或两个存储都看不到。如果它们不是真正同时发生的,那么就有可能先看到一个。 (2认同)
  • @pveentjer:保持一致意味着读取永远不会观察到过时的值。因此,写入传播到其他核心时不存在“延迟”,这将为重新排序提供一个窗口。MESI 一致性提供了与零延迟互连相同的正确性/排序行为,或者就好像所有内核都直接共享单个多端口缓存一样。显然,这不能重新排序对共享内存的任何访问,因此任何重新排序都仅限于单核内部的访问。我不知道为什么你说*缓存*只能保证单个地址的单独总订单。 (2认同)
  • @pveentjer:不引入任何重新排序意味着提交到缓存的真实顺序成为存储的总顺序。当存储提交到 L1d 行时,它就是 MESI 维护的一致状态的一部分。因此存在总商店订单。使用以已知顺序观察共享状态的有序加载,读者可以观察该顺序。也许我在这里没有使用正确的正式术语(一致性与一致性),但请考虑缓存实际上是如何工作的,而不仅仅是一致性等术语的正式定义。 (2认同)
  • @pveentjer:我想你的意思是分隔线。没有什么可以说一个发生在另一个之前,因此观察者不能对顺序有不同意见。这满足 x86 TSO。如果您不喜欢事件真正同时发生,那么假设的全序(不保证可直接观察到,只是至少在理论上存在)可以选择任一顺序。也许我的回答应该说“核心不能*不同意*关于总顺序”,而不是将其表述为核心能够确定总顺序实际上是什么。 (2认同)
  • @BeeOnRope:IRIW 中的读者必须进行获取加载,否则它与本地加载重新排序无法区分。如果第二个负载的线路较早到达,则接收这些传入线路的核心必须检查第二个负载结果在第一个负载到达后是否仍然有效。(即块LoadLoad重新排序)。也许有效地做到这一点需要互连/缓存一致性机制的一些帮助。也许我已经表明,任何支持 acq / rel 排序的系统都排除了 IRIW,这确实(?)需要互连来避免重新排序,仅适用于 CPU 内核。 (2认同)

归档时间:

查看次数:

511 次

最近记录:

4 年,10 月 前