当带有指令的内存被另一个内核更改时,CPU 管道会发生什么情况?

What happens to the CPU pipeline when the memory with the instructions is changed by another core?

我正在尝试了解 CPU 管道的“获取”阶段如何与内存交互。

假设我有这些说明:

4:  bb 01 00 00 00          mov    ,%ebx
9:  bb 02 00 00 00          mov    ,%ebx
e:  b3 03                   mov    ,%bl

如果 CPU1 将 00 48 c7 c3 04 00 00 00 写入内存地址 8(即 64 位对齐)而 CPU2 正在执行时会发生什么这些相同的说明?指令流会自动从 2 条指令变为 1 条指令,如下所示:

4:  bb 01 00 00 00          mov    ,%ebx
9:  48 c7 c3 04 00 00 00    mov    ,%rbx

由于 CPU1 正在写入 CPU2 正在读取的同一内存,因此存在争用。 写入会导致 CPU2 管道在刷新其 L1 缓存时停止吗? 假设 CPU2 刚刚完成 mov 的“获取”pĥase,是否会被丢弃以重新获取更新的内存?

此外,将 2 条指令更改为 1 条指令时存在原子性问题。

我找到了这个 quite old document 提到“指令获取单元在每个时钟周期从指令缓存存储器中获取一个 32 字节缓存行” 我认为这可以解释为每条指令都从 L1 获取缓存行的新副本,即使它们共享相同的缓存行。 但我不知道 if/how 这适用于现代 CPUs.

如果以上是正确的,那就意味着在将 mov 提取到管道后,下一次提取可能会在地址 e 处获取更新后的值并尝试执行 00 00 (add %al,(%rax)) 这可能会失败。

但是如果 mov 的获取将 mov 带入“指令缓存”,它会不会 认为下一次提取只会从该缓存(和 return mov )中获取指令而不重新查询 L1 是否有意义? 这将有效地使这 2 条指令的获取成为原子的,只要它们共享一个高速缓存行。

那是哪一个?基本上有太多未知数,太多我只能推测,所以我真的很感激逐个时钟周期分解管道的 2 个获取阶段如何与它们访问的内存交互(更改)。

它因实现而异,但通常由多处理器的 cache coherency protocol 管理。简而言之,当 CPU1 写入内存位置时,该位置将在系统中的所有其他缓存中失效。因此,写入将使 CPU2 的指令缓存中的行以及 CPU2 的 uop 缓存中的任何(部分)解码指令无效(如果它有这样的东西)。因此,当 CPU2 转到 fetch/execute 下一条指令时,所有这些缓存都将丢失,并且在重新获取内容时它会停止。根据缓存一致性协议,这可能涉及等待写入到达内存,或者可能直接从 CPU1 的 dcache 获取修改后的数据,或者可能通过一些共享缓存。

正如 Chris 所说,RFO(读取所有权)可以随时使 I-cache 行无效。

根据超标量获取组的排列方式,在 9: 处获取 5 字节 mov 之间但在 [=12= 处获取下一条指令之前,缓存行可能会失效].

当获取最终发生时(该核心再次获取缓存行的共享副本),RIP = e 并且它将获取 mov ,%rbx 的最后 2 个字节。 交叉修改代码需要确保没有其他内核在它要写入一条长指令的中间执行。

在这种情况下,您会得到 00 00 add %al, (%rax)

另请注意,写作CPU需要确保修改是原子的,例如使用 8 字节存储(Intel P6 和更高版本 CPUs 保证在 1 个缓存行内的任何对齐处存储最多 8 个字节是原子的;AMD 没有),或 lock cmpxchglock cmpxchg16b.否则 reader 可能会看到部分更新的说明。您可以将指令获取视为执行原子 16 字节加载或类似操作。


"The instruction fetch unit fetches one 32-byte cache line in each clock cycle from the instruction cache memory" which I think can be interpreted to mean that each instruction gets a fresh copy of the cache line from L1,

没有

那个宽取块然后被解码成多个 x86 指令! wide fetch 的要点是一次拉入多条指令,而不是每条指令分别重做。该文档似乎是关于 P6(Pentium III)的,尽管 P6 一次只执行 16 字节的实际提取,进入一个 32 字节宽的缓冲区,让 CPU 获取 16 字节 window。

P6是3宽的超标量,每个时钟周期最多可以解码16字节的机器码,最多包含3条指令。 (但是有一个预解码阶段可以先找到指令长度...)

参见 Agner Fog 的微架构指南(https://agner.org/optimize/) for details, (with a focus on details that are relevant for turning software performance.) Later microarchitectures add queues between pre-decode and decode. See those sections of Agner Fog's microarch guide, and https://realworldtech.com/merom/(核心 2)。

当然还有 https://realworldtech.com/sandy-bridge for more modern x86 with a uop cache. Also https://en.wikichip.org/wiki/amd/microarchitectures/zen_2#Core 最近的 AMD。

为了在阅读任何这些内容之前获得良好的背景知识,Modern Microprocessors: A 90-Minute Guide!


有关修改其自身代码的核心,请参阅:Observing stale instruction fetching on x86 with self-modifying code - 这是不同的(且更难),因为必须从较早的代码获取与较早的代码获取中整理出商店的乱序执行。稍后按程序顺序说明。也就是说,商店必须变得可见的时刻是固定的,不像另一个核心,它只是在它发生时发生。