2

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

假设我有这些说明:

4:  bb 01 00 00 00          mov    $1,%ebx
9:  bb 02 00 00 00          mov    $2,%ebx
e:  b3 03                   mov    $3,%bl

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

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

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

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

我发现这个很老的文档 提到“指令获取单元在每个时钟周期从指令高速缓存中获取一个 32 字节的高速缓存行”,我认为这可以解释为每条指令从L1,即使它们共享相同的缓存行。但我不知道这是否/如何适用于现代 CPU。

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

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

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

4

2 回答 2

3

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

于 2021-06-15T15:51:31.940 回答
3

正如 Chris 所说,RFO(Read For Ownership)可以随时使 I-cache 行无效。

根据超标量提取组的排列方式,缓存行可以在 提取 5 字节之间mov9:在 提取下一条指令之前无效e:

当 fetch 最终发生时(该核心再次获得缓存行的共享副本),RIP =e它将获取mov $4,%rbx. 交叉修改代码需要确保没有其他内核在它想要编写一条长指令的中间执行。

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

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


“指令获取单元在每个时钟周期从指令高速缓存中获取一个 32 字节的高速缓存线”,我认为这可以解释为每条指令从 L1 获取高速缓存线的新副本,

不。

然后将该宽提取块解码为多个 x86 指令!宽取指的目的是一次拉入多条指令,而不是为每条指令单独重做。该文档似乎是关于 P6(Pentium III)的,尽管 P6 一次只执行 16 个字节的实际提取,进入一个 32 字节宽的缓冲区,让 CPU 占用一个 16 字节的窗口。

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

有关详细信息,请参阅 Agner Fog 的微架构指南 ( https://agner.org/optimize/ ),(重点关注与提高软件性能相关的细节。)后来的微架构在预解码和解码之间添加了队列。请参阅 Agner Fog 的微架构指南的这些部分,以及https://realworldtech.com/merom/(核心 2)。

当然,请参阅https://realworldtech.com/sandy-bridge了解更现代的带有 uop 缓存的 x86。对于最近的 AMD,还有https://en.wikichip.org/wiki/amd/microarchitectures/zen_2#Core 。

在阅读其中任何一篇之前,为了获得良好的背景知识,现代微处理器:90 分钟指南!.


对于修改自己代码的核心,请参阅:Observing stale instruction fetching on x86 with self-modifying code - 这是不同的(并且更难)因为商店的乱序执行必须从早期的代码获取中排序出来 vs . 以后按程序顺序说明。即商店必须变得可见的时刻是固定的,与另一个核心不同,它只是在它发生时发生。

于 2021-06-15T16:56:40.563 回答