2

英特尔架构软件开发人员手册, 2012 年 8 月,第一卷。3A 节。8.2.2:

除了那些执行存储的处理器之外,任何两个存储都以一致的顺序被处理。

但可以这样吗?

我问的原因是:考虑一个具有超线程的双核 Intel i7 处理器。根据手册的卷。如图 1 所示,图 2-8,i7 的逻辑处理器 0 和 1 共享一个 L1/L2 缓存,但其逻辑处理器 2 和 3 共享一个不同的 L1/L2 缓存——而所有逻辑处理器共享一个 L3 缓存。假设逻辑处理器 0 和 2(不共享 L1/L2 高速缓存)几乎在同一时间写入相同的内存位置,并且目前写入的深度不超过 L2。逻辑处理器 1 和 3(它们是“执行存储的处理器以外的处理器”)不能看到“两个存储顺序不一致”吗?

为了实现一致性,逻辑处理器 0 和 2 是否必须发出 SFENCE 指令,而逻辑处理器 1 和 3 是否必须发出 LFENCE 指令?尽管如此,该手册似乎不这么认为,它对此事的看法并不仅仅是印刷错误。看起来是故意的。我很困惑。

更新

根据@Benoit 的回答,提出以下问题:因此,L1 和 L2 的唯一目的是加速负载。加速存储的是 L3。是对的吗?

4

3 回答 3

3

英特尔 CPU(与所有普通 SMP 系统一样)使用MESI (一种变体)来确保缓存加载/存储的缓存一致性。即所有内核通过它们的缓存看到相同的内存视图。

核心只能在执行读取所有权 (RFO) 后写入缓存行,使该行处于独占状态(没有其他缓存具有可以满足负载的行的有效副本)。相关:原子 RMW 操作通过在操作期间将其锁定在已修改状态来防止其他内核对目标缓存行执行任何操作。

要测试这种重新排序,您需要另外两个线程,它们都读取两个存储(以相反的顺序)。 您提出的方案有一个核心(reader2)在另一个核心(reader1)读取 writer1 存储的同一行的新值之后从内存(或 L3,或其自己的私有 L2/L1)读取旧值。这是不可能的:要让 reader1 看到 writer1 的存储,writer1 必须已经完成了一个 RFO,该 RFO 使任何地方的缓存行的所有其他副本都无效。并且不允许直接从 DRAM 读取而不(有效地)窥探任何回写缓存。(维基百科的 MESI 文章有图表。)

当存储提交(从内核内部的存储缓冲区)到 L1d 缓存时,它同时对所有其他内核全局可见。 在此之前,只有本地核心才能“看到”它(通过存储缓冲区的存储-> 加载转发)。

在数据从一个核心传播到另一个核心的唯一方法是通过全局缓存一致性域的系统上,MESI 缓存一致性单独保证存在单个全局存储顺序,所有线程都可以达成一致。x86 强大的内存排序规则使这种全局存储顺序成为程序顺序的某种交错,我们称之为 Total Store Order 内存模型。

x86 的强内存模型不允许 LoadLoad 重新排序,因此加载按程序顺序从缓存中获取数据,而读取线程中没有任何屏障指令。1

在从相干缓存中获取数据之前,负载实际上会窥探本地存储缓冲区。 这就是您引用的一致顺序规则排除了任一存储由执行负载的同一核心完成的情况的原因。有关加载数据真正来自何处的更多信息, 请参阅全局不可见加载说明。但是当加载地址不与任何最近的存储重叠时,我上面所说的适用:加载顺序是从共享的全局一致缓存域中采样的顺序。


一致的顺序规则是一个非常薄弱的​​要求。许多非 x86 ISA 并不能在纸面上保证这一点,但很少有实际的(非 x86)CPU 设计具有一种机制,通过该机制,一个内核可以在所有内核全局可见之前查看来自另一个内核的存储数据。IBM POWER with SMT 就是这样一个例子:在不同线程中对不同位置的两次原子写入是否总是以相同的顺序被其他线程看到?解释了一个物理核心内的逻辑核心之间的转发如何导致它。(这与您提出的类似,但在存储缓冲区而不是 L2 中)。

具有超线程(或 AMD 在 Ryzen 中的 SMT)的 x86 微架构通过在一个物理内核上的逻辑内核之间静态分区存储缓冲区来满足这一要求。 在一个带有 HT 的 Core 上执行的线程之间的数据交换将使用什么? 因此,即使在一个物理核心中,存储也必须提交到 L1d(并成为全局可见),然后另一个逻辑核心才能加载新数据。

不将一个逻辑核心中的已退休但未提交的存储转发到同一物理核心上的其他逻辑核心可能更简单。

(x86 的 TSO 内存模型的其他要求,例如按程序顺序出现的加载和存储,更难。现代 x86 CPU 执行乱序,但使用内存顺序缓冲区来维持错觉,并将存储按程序顺序提交给 L1d。负载可以推测性地取值早于“应该”,然后再检查。这就是英特尔 CPU 具有“内存顺序错误推测”管道核弹的原因: 生产者-消费者共享的延迟和吞吐量成本是多少超兄弟姐妹与非超兄弟姐妹之间的记忆位置?。)

正如@BeeOnRope 指出的那样,HT 和保持没有 LoadLoad 重新排序的错觉之间存在交互通常,CPU 可以检测到另一个内核何时在负载实际读取缓存行之后但在架构上允许读取缓存行之前检测到它:加载端口可以跟踪该缓存行的失效。但是对于 HT,加载端口还必须窥探其他超线程提交到 L1d 缓存的存储,因为它们不会使行无效。(其他机制是可能的,但如果 CPU 设计人员想要“正常”负载的高性能,这是他们必须解决的问题。)


脚注 1:在弱排序 ISA 上,您将使用加载排序障碍来控制每个读取器中的 2 个加载从全局一致的缓存域中获取数据的顺序。

编写器线程每个只做一个存储,所以栅栏是没有意义的。因为所有核心共享一个一致的缓存域,所以栅栏只需要控制核心内的本地重新排序。每个核心中的存储缓冲区已经尝试尽快使存储在全局范围内可见(同时尊重 ISA 的排序规则),因此屏障只会让 CPU 在执行后续操作之前等待。

x86lfence基本上没有内存排序用例,sfence仅对 NT 存储有用。只有mfence当一个线程正在写东西然后读取另一个位置时,才对“正常”的东西有用。 http://preshing.com/20120515/memory-reordering-caught-in-the-act/。因此它会阻止 StoreLoad 重新排序和跨屏障存储转发。


根据@Benoit 的回答,提出以下问题:因此,L1 和 L2 的唯一目的是加速负载。加速存储的是 L3。是对的吗?

不,L1d 和 L2 是回写式缓存: 英特尔酷睿 i7 处理器中使用了哪种缓存映射技术?. 重复存储到同一行可以被 L1d 吸收。

但是 Intel 使用的是包容性的 L3 缓存,那么一个内核中的 L1d 怎么会有唯一的副本呢?L3 实际上是包含标签的,这就是 L3 标签作为窥探过滤器所需的全部内容(而不是向每个核心广播 RFO 请求)。脏行中的实际数据对于每个内核的内部缓存是私有的,但是 L3 知道哪个内核具有该行的当前数据(因此当另一个内核想要读取另一个内核在 Modified 中具有的行时,该向哪里发送请求状态)。干净的高速缓存行(处于共享状态)包含 L3 的数据,但写入高速缓存行不会直写到 L3。

于 2018-06-26T00:16:53.747 回答
1

我相信英特尔文档所说的是 x86 芯片的机制将确保其他处理器始终以一致的顺序看到写入。

因此,其他处理器在读取该内存位置时只会看到以下结果之一:

  • 写入之前的值(即读取先于两次写入)

  • 处理器 0 写入后的值(即好像处理器 2 先写入,然后处理器 0 覆盖)

  • 处理器 2 写入后的值(即好像处理器 0 先写入然后处理器 2 覆盖)

处理器 1 无法看到处理器 0 写入后的值,但同时让处理器 3 看到处理器 2 写入后的值(反之亦然)。

请记住,由于允许处理器内重新排序(请参阅第 8.2.3.5 节),处理器的 0 和 2 可能会看到不同的情况。

于 2013-01-09T05:00:45.510 回答
0

哎呀,这是一个棘手的问题!但我会尝试...

写入不超过 L2

基本上这是不可能的,因为英特尔使用的是包容性缓存。写入 L1 的任何数据也将发生在 L2 和 L3 中,除非您通过 CR0/MTRR 禁用它们来防止缓存。

话虽如此,我猜有仲裁机制:处理器发出写入数据的请求,仲裁器从每个请求队列的未决请求中选择授予哪个请求。选择的请求被广播到窥探者,然后被缓存。我想它会防止竞争,强制执行除了执行请求的处理器之外的处理器看到的一致顺序。

于 2013-01-09T14:45:53.893 回答