当指令不在 L1I 中时,IFU 和前端会发生什么情况?

What happens with the IFU and the front end when an instruction is not in L1I?

首先,当 IFU 发出 16 个字节的请求时,这种与 L1I 的交互 modified/fixed 使得当 L1I 从 IFU 接收到一个地址时,它随后会连续产生 16 个字节,或者 IFU必须像传统缓存访问一样发送所有 16 个字节的地址?

直截了当,假设 IFU 正在 16B 对齐边界获取指令,然后突然获取虚拟索引(我假设虚拟索引确实是逻辑虚拟而不是线性虚拟——不完全确定;我知道在 L1D 中,AGU 处理 L1i 缓存中的分段偏移)未命中。

究竟会发生什么? (注意:示例 CPU 具有环形总线拓扑的 Skylake)

解码器解码完之前是否关闭前端,如何实现?其次,IFU和L1I cache之间有什么样的协商/对话,有miss,cache必须通知IFU停止取指令?也许缓存等待从下层接收数据,一旦收到,就将数据发送给 IFU,或者 IFU 是否在自旋锁状态下等待并继续尝试读取?

让我们假设它想要的数据在 DDR4 模块上,而不是在缓存子系统中——如果一个不稳定的程序给硬件预取器带来困难,这是可能的。我想把这个过程记在心里。

如果有人对此过程有更多的信息,可以进一步启发我,请告诉我。

有趣的问题,一旦你克服了一些误解(请参阅我对这个问题的评论)。

Fetch/decode 严格按照程序顺序发生。没有机制在等待 L1i 未命中时从后面的缓存行解码块,甚至没有填充 uop 缓存。我的理解是,uop-cache 只填充了 CPU 期望沿着当前执行路径实际执行的指令。

(x86 的可变长度指令意味着您需要在开始解码之前知道指令边界。如果分支预测说缓存未命中指令块将在另一条缓存行中的某处分支,则这可能是可能的,但是当前的硬件不是那样构建的。没有地方可以将解码后的指令放在 CPU 可以返回并填补空白的地方。)


L1i 中有硬件预取(我假设它确实利用了分支预测来知道下一个分支的位置,即使当前获取在缓存未命中时被阻止),因此代码获取可以并行生成多个未完成的负载以更好地占用内存管道。

但是,是的,L1i 未命中会在管道中产生一个气泡,该气泡一直持续到数据从 L2 到达为止。每个核心都有自己私有的每核心 L2,如果它在 L2 中未命中,它负责将请求发送到核心之外。 WikiChip shows L2 和 L1i 之间的数据路径在 Skylake-SP 中是 64 字节宽。

https://www.realworldtech.com/haswell-cpu/6/ 显示 L2<->L1d 在 Haswell 及更高版本中为 64 字节宽,但没有显示指令获取的详细信息。 (这通常不是瓶颈,特别是对于命中 uop 缓存的中小型循环。)

获取、预解码(指令边界)和完全解码之间存在队列,可以隐藏/吸收这些气泡,有时会阻止它们到达解码器并实际上损害解码吞吐量。 还有一个更大的队列(在 Skylake 上为 64 微指令)提供给 issue/rename 阶段,称为 IDQ。指令从 uop 高速缓存或传统解码添加到 IDQ。 (或者当一条指令的微码间接微指令到达 IDQ 的前端时,issue/rename 直接从微码定序器 ROM 中获取,对于 rep movsblock cmpxchg.)

但是当一个阶段没有输入数据时,是的,它会掉电。没有"spin-lock";它不管理对共享资源的独占访问,它只是根据流量控制信号等待。

当代码获取命中 uop 缓存时也会发生这种情况:传统解码器也可能会断电。省电是 uop 缓存的好处之一,环回缓冲区为 uop 缓存省电。


L1I cache controller allocates a line fill buffer

L2->L1i 使用的缓冲区与 L1d 缓存/NT 存储使用的 10 个 LFB 不同。这10个专用于L1d和L2之间的连接。

Skylake-SP block diagram on WikiChip 显示从 L2 到 L1i 的 64 字节数据路径,与 L2->L1d 及其 10 个 LFB 分开。

L2 必须管理多个读取器和写入器(L1 缓存和数据 to/from L3 在其 SuperQueue 缓冲区上)。 我们知道 L2 每个时钟周期可以处理 2 次命中,但是它可以处理/生成 L3 请求的每个周期的未命中数不太清楚。

Hadi 还评论说:L2 有一个用于 L1i 的读取 64 字节端口和一个用于 L1d 的双向 64 字节端口。它还有一个 read/write 端口(在 Skylake 中为 64 字节,在 Haswell 中为 32 字节)及其连接的 L3 切片。当L2 controller从L3接收到一条线时,它立即将其写入对应的superqueue entry(或多个entries)。

我还没有检查过这方面的主要来源,但对我来说听起来不错。


一次从 DRAM 中获取 64 字节(1 个缓存行)的突发传输。不只是 16 个字节(128 位)!可以从 "uncacheable" 内存区域执行代码,但通常您使用的是可缓存的 WB(回写)内存区域。

据我所知,即使是 DDR4 也有 64 字节的突发大小,而不是 128 字节。

I assume this is all kept in queue order, so the home agent knows what address the data corresponds to.

不,内存控制器可以在 DRAM 页面(与虚拟内存页面不同)内对局部性请求重新排序。

返回内存层次结构的数据有一个与之关联的地址。它由 L3 和 L2 缓存,因为它们具有写入分配缓存策略。

当它到达 L2 时,未完成的请求缓冲区(来自 L1i)与地址匹配,因此 L2 将该行转发给 L1i。依次匹配地址并唤醒正在等待的取指令逻辑。

@HadiBrais 评论道:L2 的请求需要用发送者 ID 标记。 L3 的请求需要用另一个发送者 ID 标记。 L1I的请求不需要打标签。

Hadi 还讨论了 L3 需要在每个周期处理来自多个核心的请求这一事实。 Skylake-SP / SKX 之前 CPUs 中的环形总线架构意味着每个时钟最多 3 个请求可以到达单个 L3 切片(一个在环上的每个方向上,一个来自连接到它的核心) .如果它们都用于同一个缓存行,那么从这个切片中一次获取就可以满足它们肯定是有利的,所以这可能是 L3 缓存切片所做的事情。


另见 Ulrich Drepper 的 What Every Programmer Should Know About Memory? for more about cache and especially about DDR DRAM. Wikipedia's SDRAM article 还解释了 DRAM 中整个高速缓存行的突发传输是如何工作的

我不确定英特尔 CPUs 是否真的在缓存行内传递了一个偏移量,用于关键字优先和提前重启备份缓存层次结构。我猜不会,因为一些更接近核心的数据路径比 8 字节宽得多,在 Skylake 中是 64 字节宽。

另请参阅 Agner Fog 的 microarch pdf (https://agner.org/optimize/), and other links in the x86 tag wiki.