在此示例中,数据缓存如何路由对象?

Posted

技术标签:

【中文标题】在此示例中,数据缓存如何路由对象?【英文标题】:How do data caches route the object in this example? 【发布时间】:2019-02-11 19:09:17 【问题描述】:

考虑图表数据缓存架构。 (ASCII艺术随后。)

  --------------------------------------
  | CPU core A | CPU core B |          |
  |------------|------------| Devices  |
  |  Cache A1  |  Cache B1  | with DMA |
  |-------------------------|          |
  |         Cache 2         |          |
  |------------------------------------|
  |                RAM                 |
  --------------------------------------

假设

一个对象在缓存 A1 的 dirty 行上出现阴影, 同一对象的旧版本在缓存 2 的 clean 行上被遮蔽,并且 同一对象的最新版本最近已通过 DMA 写入 RAM。

图表:

  --------------------------------------
  | CPU core A | CPU core B |          |
  |------------|------------| Devices  |
  |  (dirty)   |            | with DMA |
  |-------------------------|          |
  |     (older, clean)      |          |
  |------------------------------------|
  |          (newest, via DMA)         |
  --------------------------------------

请提三个问题。

    如果 CPU 内核 A 尝试加载(读取)对象,会发生什么?

    如果 CPU 内核 A 尝试存储(写入)对象,会发生什么?

    如果加载或存储的是内核 A 而不是内核 B,是否会发生任何不明显、有趣和/或不同的事情?

我的问题是理论上的。我的问题不涉及任何特定的 CPU 架构,但如果您愿意,您可以在回答中提及 x86 或 ARM(甚至 RISC-V)。

注意事项。 如果忽略窥探会简化您的答案,那么您可以自行决定忽略窥探。或者,如果修改后的问题可以更好地阐明您认为的主题,您可以修改问题。如果您必须编写代码来回答,那么我更喜欢 C/C++。据我所知,您不需要在答案中命名MESI 或 MOESI 协议的特定标志,但更简单、不太详细的答案可能就足够了。

动机。 我问的动机是我正在阅读 C++ 标准中的并发和内存模型。如果可能的话,我想学习在硬件操作方面大致可视化这个模型。

更新

据我所知,@HadiBrais 建议以下图表架构比我之前绘制的架构更常见,特别是如果实现了 DDIO(请参阅下面的答案)。

  --------------------------------------
  | CPU core A | CPU core B | Devices  |
  |------------|------------| with DMA |
  |  Cache A1  |  Cache B1  |          |
  |------------------------------------|
  |              Cache 2               |
  |------------------------------------|
  |                RAM                 |
  --------------------------------------

【问题讨论】:

你所说的“阴影”是什么意思?此外,您的示例表明您假设 DMA 是非连贯的。需要明确的是,这是故意的吗? @HadiBrais 澄清提出问题的层次:我是一名 Debian 开发人员,受过电气工程教育。我的专业领域不是计算机,而是建筑,所以我主要是在计算机方面自学。如果“阴影”是错误的词,请纠正我!关于 DMA,我确实一直假设 DMA 是非连贯的,但 PCI 等对我来说并不为人所知。我提到 DMA 只是为了简化问题。 (否则,问题可能需要四个 CPU 内核和一个三级缓存,这会不必要地复杂化。) 特别是回答您的第一个问题,通过“隐藏 X”,我的意思是“暂时保留 X 的不完全同步、可能可修改的副本以供本地使用,并在必要时用于以后刷新。” 现代 x86 具有缓存一致性 DMA。我认为当 x86 CPU 开始将内存控制器放在芯片上时,这成为了一件事情,因此在通往内存的路上窥探 L3 标签变得实用(并且英特尔的包容性 L3 标签用作私有每核缓存的窥探过滤器)。拥有一致的 DMA 可能会使激进的硬件预取更容易实现,而不必担心在分支错误预测的奇怪极端情况下会产生退相干,从而导致刚刷新的内存中出现不需要的推测性负载。 【参考方案1】:

您的假设系统似乎包括一致的回写式 L1 缓存和非一致的 DMA。一个非常相似的真实处理器是ARM11 MPCore,只是它没有二级缓存。但是,大多数现代处理器确实具有相干 DMA。否则,软件有责任确保一致性。如图所示的系统状态已经不连贯了。

如果 CPU 内核 A 尝试加载(读取)对象,会发生什么?

它只会读取本地 L1 缓存中保存的行。不会发生任何变化。

如果 CPU 内核 A 尝试存储(写入)对象,那么 发生了什么?

这些行在核心A的L1缓存中已经处于M一致性状态,所以可以直接写入。不会发生任何变化。

如果发生任何不明显、有趣和/或不同的事情, 而不是核心A,核心B做了加载或存储?

如果核心B向同一行发出加载请求,则窥探核心A的L1缓存并发现该行处于M状态。该行在 L2 缓存中更新,并被发送到核心 B 的 L1 缓存。还会发生以下情况之一:

内核 A 的 L1 缓存中的行无效。该行以 E 一致性状态(在 MESI 协议的情况下)或 S 一致性状态(在 MSI 协议的情况下)插入核心 B 的 L1 缓存。如果 L2 使用探听过滤器,则更新过滤器以指示核心 B 的线路处于 E/S 状态。否则,L2 中线路的状态将与核心 B 的 L1 中的状态相同,只是它不知道它在那里(因此窥探必须始终广播)。 核心 A 的 L1 缓存中的行状态更改为 S。该行以 S 一致性状态插入核心 B 的 L1 缓存。 L2 在 S 状态下插入行。

无论哪种方式,L1 缓存和 L2 缓存都将保存同一行的副本,这与内存中的行保持不连贯。

如果核心 B 向同一行发出存储请求,则该行将从核心 A 的缓存中失效,并最终在核心 B 的缓存中处于 M 状态。

最终,该行将从缓存层次结构中逐出,为其他行腾出空间。发生这种情况时,有两种情况:

该行处于 S/E 状态,因此将简单地从所有缓存中删除。稍后,如果再次读取该行,则将从主存中读取 DMA 操作写入的副本。 该行处于 M 状态,因此它将被写回主内存并(可能部分)覆盖由 DMA 操作写入的副本。

显然,这种不连贯的状态绝不会发生。可以通过在 DMA 写操作开始之前使所有高速缓存中的所有相关行无效并确保在操作完成之前没有内核访问正在写入的内存区域来防止这种情况。每当操作完成时,DMA 控制器都会发送中断。在读取 DMA 操作的情况下,需要将所有相关行写回内存,以确保使用最新的值。

Intel Data Direct I/O (DDIO) 技术使 DMA 控制器能够直接从共享的最后一级缓存读取或写入以提高性能。


这部分与问题没有直接关系,但我想写在某个地方。

所有商业 x86 CPU 都是完全一致的缓存(即整个缓存层次结构是一致的)。更准确地说,同一共享内存域中的所有处理器都是高速缓存一致的。此外,所有商业 x86 众核协处理器(即 PCIe 卡形式的英特尔至强融核)内部完全一致。协处理器是 PCIe 互连上的设备,与其他协处理器或 CPU 不一致。因此,协处理器位于它自己的一个单独的相干域中。我认为这是因为没有内置的硬件机制可以使具有缓存的 PCIe 设备与其他 PCIe 设备或 CPU 保持一致。

除了商业 x86 芯片之外,还有一些原型 x86 芯片不是缓存一致的。我知道的唯一例子是英特尔的Single-Chip Cloud Computer (SCC),它后来演变成连贯的 Xeon Phi。

【讨论】:

@thb:如果在 L1d 中是脏的,那么在 L2 中拥有 (older, clean) 副本是不可能的。正如哈迪所说,这已经是语无伦次了。但不仅是 RAM,缓存级别之间也是如此。 MESI 要求如果一行在任何缓存中处于修改(脏)状态,则所有 其他 缓存都将其设为无效。 (通常一行在从 L1 驱逐之前不会被写回,所以我们最终会在 L2 中得到 Modified 而在其他 L1 中是 Invalid。但是有一种机制可以让内部缓存能够在 L2 的脏线实际出现之前读取它的副本回写。) 纯原始 MESI 是关于保持 单独 缓存的一致性(例如,多个内核,每个内核都有自己的私有缓存),而不是具有共享最后一级缓存的层次结构。这就是为什么它谈论窥探内存总线的原因。 (我想就这个问题发表之前的评论,但把它放在这里以防哈迪想要更新答案以解决这个问题,初始状态超出了 DMA 不连贯性,进入无效的 MESI 状态。)跨度> @PeterCordes 我将(older, clean) 解释为该行存在于E 状态。一个实现可以检查如果 L2 的线路处于 E 或 M 状态,那么它必须窥探所有私有缓存。同样在另一个实现中,MESI 只能在 L1 上实现,而 SI 可以在包容性 L2 上实现。在这种情况下,如果任何核心在其私有缓存中未命中并在 L2 中命中,它必须始终窥探所有私有缓存。这效率较低,但在 L2 需要较少的硬件。如果 L2 不包含在内,则无论 L2 中有命中还是未命中,都必须发送窥探... ...MESI 协议基本上只在您进入 L1 时才重要。如果 OP 打算让 MESI 在 L2 并且意味着该线路在那里处于 S 状态,那么它确实是不连贯的,因为另一个内核可以读取 L2 中的陈旧版本(不连贯的 DMA 除外)。 为什么系统应该有一致的缓存?是否有可能在核心之间有一个不连贯的公共 L2,不是吗?英特尔 SCC 就是这种类型的东西(尽管它在内核之间没有公共缓存)。

以上是关于在此示例中,数据缓存如何路由对象?的主要内容,如果未能解决你的问题,请参考以下文章

vue用了vuex和路由的缓存,出问题了一个页面添加按钮进去后一直都有上一次添加的数据参数在上面如何修改?

jQuery源代码学习之六——jQuery数据缓存Data

TP5 生成数据库字段 和 路由 缓存来提升性能

jQuery.getJSON:如何避免在每次刷新时请求 json 文件? (缓存)

哪个缓存最友好?

js如何获取缓存