【问题标题】:The ordering of L1 cache controller to process memory requests from CPUL1 缓存控制器处理来自 CPU 的内存请求的顺序
【发布时间】:2020-11-23 17:17:51
【问题描述】:

在总存储顺序 (TSO) 内存一致性模型下,x86 cpu 将有一个写入缓冲区来缓冲写入请求,并且可以从写入缓冲区处理重新排序的读取请求。它表示写缓冲区中的写请求将退出并按FIFO顺序向缓存层次结构发出,这与程序顺序相同。

我很好奇:

为了服务从写缓冲区发出的写请求,L1缓存控制器是否处理写请求,完成写请求的缓存一致性,并按照与发出顺序相同的顺序将数据插入L1缓存?

【问题讨论】:

  • 跟随TSO,看来L1缓存也应该按顺序处理存储请求。
  • finish the cache coherence of the write requests 是什么意思?但是,是的,存储数据进入 L1 缓存并在 x86 上按顺序全局可见,因为存储退出。在商店准备退役之前,它的数据只存在于store queue 中,这不是全局可见的,并且只会被当前核心的负载窥探。如果我找不到现有的重复问题,我会将其作为答案发布。
  • 我在How does memory reordering help processors and compilers? 的回答中对此进行了详细介绍。查看“实际 CPU 的工作原理”部分。
  • 为什么我指出“完成写请求的缓存一致性”是因为我认为只有在缓存一致性之后,数据才能全局可见,在这一点之后,其他核心可以得到更新缓存一致性数据,如监听协议。
  • 为什么我对这个问题感到困惑是缓存控制器可以以非阻塞方式处理请求。但是,为了符合 TSO 并确保数据在多核系统上全局可见,缓存控制器是否应该遵循存储顺序?因为如果在核心 1 上有两个变量 A 和 B 被顺序更新,核心 2 从核心 1 获取更新的 B,那么核心 2 也必须能看到更新的 A。为了实现这一点,我认为核心上的私有缓存层次结构1 必须按顺序完成变量 A 和 B 的缓存一致性并使它们全局可见。我说的对吗?

标签: x86 hardware cpu-architecture cpu-cache memory-barriers


【解决方案1】:

你的术语很不寻常。你说“完成缓存一致性”;实际发生的情况是,核心必须在修改它之前获得缓存行的(独占)所有权。在修改发生的瞬间/周期,它成为缓存一致性协议中所有参与者共享的内存内容视图的一部分。

所以是的,您确实“完成了缓存一致性” = 获得独占所有权商店甚至可以进入缓存并成为全局可见 = 可用于共享该缓存行的请求。缓存始终保持一致性(这就是 MESI 的重点),不会失去同步然后等待一致性。我认为你的困惑源于你的心智模型与现实相匹配。

(弱排序架构具有令人费解的可能性,例如并非所有内核都以相同的顺序查看其他两个内核的存储;private store-forwarding between SMT threads on one physical core letting another logical core see a store ahead of commit to L1d = global visibility 可能会发生这种情况。)


我想你知道一些,但让我从基础开始。

每个内核中的 L1 缓存都参与了缓存一致性协议,该协议使其缓存与一致性域中的其他缓存保持一致(例如 L2 和 L3,以及其他内核中的 L1,但不包括 GPU 内的视频 RAM 缓存) .

负载在从 L1 缓存(or from the store buffer 或不可缓存的 RAM 或 MMIO)读取数据的那一刻变得全局可见。 MFENCE 可以强制他们等待较早的商店在对 L1 采样之前成为全球可见的 avoid StoreLoad reordering

存储在其数据提交到 L1 缓存的那一刻变得全局可见。发生这种情况所需的条件是:

  • 已完成执行:数据+地址在存储缓冲区条目中。 (即,一旦输入准备就绪,就会在适当的端口上执行存储地址和存储数据微指令,将地址和数据写入存储缓冲区,也就是 Intel CPU 上的内存顺序缓冲区)。

  • 它是来自核心无序部分的retired,因此已知是非推测性的。在退休之前,我们不知道 it and all preceding instructions won't fault,也不知道它是否处于分支错误预测或其他错误推测的阴影中。

    退休只能在完成执行后发生,但与对 L1d 的承诺无关。存储缓冲区可以继续跟踪非推测性存储,即使在 ROB(乱序执行 ReOrder 缓冲区)忘记了存储指令之后,最终肯定会发生。

  • 所有前面的加载/存储/栅栏已经是全局可见的(因为 x86 的内存排序规则)。这不包括弱序操作(NT 存储);其他负载/存储可以通过它们。

  • 缓存行在当前内核的 L1d 缓存中处于 MESI/MESIF/MOESI 缓存一致性协议的 Exclusive 或 Modified 状态。 如果 RFO (读取所有权)在外层缓存中遇到缓存未命中,或者与其他需要独占访问权限以写入或原子 RMW 缓存行的内核争用。

请参阅维基百科的MESI article,了解允许的状态转换图表和详细信息。关键是一致性是通过仅在确定没有其他缓存包含该行时才允许核心修改其缓存行的副本来实现的,因此不可能出现相同的两个冲突副本行存在。

Intel CPU 实际上使用MESIF,而 AMD CPU 实际上使用MOESI 来允许缓存->缓存数据传输脏数据,而不是像基本的 MESI 协议要求的那样写回共享的外部缓存。

另请注意,现代英特尔设计(在 Skylake-AVX512 之前)实现使用 large shared inclusive L3 cache as a backstop for cache-coherency,因此实际上不必将窥探请求广播到所有内核;他们只检查 L3 标签(其中包含额外的元数据以跟踪哪个核心正在缓存什么。
Intel 的 L3 是包含标记的,即使对于内部缓存处于 Exclusive 或 Modified 状态并因此在 L3 中无效的行也是如此。见this paper for more details of a simplified version of what Intel does)。

还相关:I wrote an answer recently about why we have small/fast L1 + larger L2/L3, instead of one big cache,包括一些指向其他缓存相关内容的链接。


回到实际问题:

是的,商店按照程序顺序提交到 L1,因为这是 x86 要求它们成为全局可见的顺序。 L1-commit order 和 global-visibility order 是一回事。

您应该说“获得缓存行的所有权”,而不是“完成缓存一致性”。这涉及使用缓存一致性协议与其他缓存进行通信,所以我猜您的意思可能是“使用缓存一致性协议完成获得独占所有权”。

MESI wiki 文章的 memory ordering 部分指出,在存储队列中缓冲存储通常与乱序执行是分开的。

存储缓冲区将提交到 L1d 与 OoO exec 退休分离。这可能会隐藏比常规无序窗口大小多 很多 的存储延迟。但是,即使中断到达,退休存储必须最终发生(以正确的顺序),因此允许大量退休但未提交的存储可能会增加中断延迟。

存储缓冲区尝试尽快将停用的存储提交到 L1d,但它受到内存排序规则的限制。 (即其他核心很快就会看到存储;您不需要栅栏来刷新存储缓冲区,除非您需要当前线程在此线程中稍后加载之前等待这种情况发生。例如,对于顺序一致的存储。)

在弱序 ISA 上,稍后的存储可以提交到 L1d,而更早的存储仍在等待缓存未命中。 (但您仍然需要一个内存顺序缓冲区来保持单核按程序顺序运行指令的错觉。)

存储缓冲区可以同时有多个缓存未命中,因为即使在强排序 x86 上,它也可以在该存储成为缓冲区中最旧的缓存行之前发送 RFO 缓存行。

【讨论】:

  • 允许 senior 存储在退休后坐在存储缓冲区中,这对于那些分散存储很少且缺少 DRAM 的代码来说可能是一个巨大的胜利,因为 ROB 窗口可以移动除了丢失的商店之外,它需要很长时间才能最终提交到 L1。这样,存储延迟有时可以比加载延迟更好地隐藏(即使存储创建依赖链的事实除外)。顺便说一句,只要效果不可见,复杂的实现可能会无序地将存储提交到 L1 - 例如通过推迟对探测的响应。没有证据表明 x86 确实如此。
  • @BeeOnRope:已更新。嘿,我最终在我的答案中提到了 RFO 预取,然后才看到你发布了一个专注于此的内容:)
  • 看起来不错!请注意mfence(以及其他暗示事物的完整障碍)停止存储转发类型的重新排序以及StoreLoad(需要一个更好的名称 - 没有一个好名字很难随便提及StoreLoad - 也许这是问题的一部分:很长一段时间以来,我都认为 only StoreLoad 重新排序,可能是因为它有一个好听的名字!)。
  • @St.Antario:是的,完全正确。加载顺序必须尊重存储,因此加载缓冲区实际上是 MOB 的一部分。
  • RS 和 ROB 是分开的,都必须有空间。 ROB 包括 RS:同时将 uops 添加到两者(或者对于不需要后端端口的 uops,只添加用于 uops 的 ROB)。并且 uops 在执行后离开 RS,但留在 ROB 中直到退休。但是RS更小。您没有阅读 Agner Fog 的 microarch PDF 或其他有关 OoO exec 工作原理的基础知识吗?
【解决方案2】:

是的,在 x86-TSO 这样的模型中,商店可能会按照程序顺序致力于 L1,Peter's answer 很好地涵盖了它。也就是说,存储缓冲区按程序顺序维护,并且内核在继续之前将仅提交最旧的存储(或者可能是几个连续的最旧存储,如果它们都进入同一缓存行)到 L1。1

但是,您在 cmets 中提到您担心这可能会通过实质上使存储缓冲区提交阻塞(序列化)进程来影响性能:

为什么我对这个问题感到困惑是缓存控制器 可以以非阻塞方式处理请求。但是,为了符合 TSO 并确保数据在多核系统上全局可见, 缓存控制器应该遵循存储顺序吗?因为如果有 是两个变量 A 和 B 在核心 1 和核心上顺序更新 2 从 core 1 获取更新后的 B,那么 core 2 也必须能看到 更新 A. 为了实现这一点,我认为私有缓存层次结构 核心 1 必须完成变量 A 和 B 的缓存一致性 排序并使它们全局可见。我说的对吗?

好消息是,即使存储缓冲区可能以有序的方式仅将最旧的存储提交到 L1,它仍然可以通过在存储缓冲区中向前看来获得与内存子系统的其余部分相关的大量并行性发出 prefetch RFO 请求:尝试在本地核心中获取处于E 状态的行,甚至在存储第一个提交到 L1 之前。

这种方法不违反顺序,因为存储仍然按程序顺序编写,但在解决 L1 存储未命中时允许完全并行。无论如何,真正重要的是 L1 存储未命中:L1 中的存储命中可以快速提交,每个周期至少 1 次,因此提交一堆命中并没有太大帮助:但是在存储未命中时获得 MLP 非常重要,尤其是对于分散的存储预取器无法处理。

x86 芯片真的使用了这样的技术吗?几乎可以确定。最令人信服的是,对一长串随机写入的测试显示平均延迟比完整内存延迟要好得多,这意味着 MLP 明显优于一个。您还可以找到像 this onethis one 这样的专利,英特尔在其中几乎准确地描述了这种方法。

不过,没有什么是完美的。有一些证据表明,当商店缺少 L1 时,订购问题会导致 weird performance hiccups,即使它们在 L2 中命中也是如此。


1 如果保持按顺序提交的错觉,它肯定可能可以无序提交存储,例如,通过在恢复顺序之前不放弃乱序写入的缓存行的所有权,但这很容易出现死锁和其他复杂情况,我没有证据表明 x86 会这样做。

【讨论】:

  • 如果L1D 存储未命中,存储uop 是否会保留LFB 条目直到RFO 完成?根据我在IOM/3.6.9 中找到的内容:在对一级缓存的写入未命中时,它允许在从更远的地方读取该缓存行以获得所有权(RFO)之前,对同一缓存行进行多次存储缓存/内存层次结构。据此,我假设在存储从 OoO 核心退役后,当需要 RFO 时,它会保留在 LFB 中,直到 RFO 完成,从而允许将多次写入合并到该缓存行。
  • @St.Antario - 在 RFO 完成之前一直持有 LFB,是的。通常,LFB 始终保持到相关的内存操作完成为止。我相信在这段时间内,同一行的其他存储可以合并到 LFB 中,并释放它们关联的存储缓冲区条目,同时允许存储缓冲区继续耗尽,即使存储错过了 L1D。
猜你喜欢
  • 2018-09-22
  • 2010-10-17
  • 2017-01-27
  • 2022-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-18
相关资源
最近更新 更多