提问人:Frontier_Setter 提问时间:10/9/2023 更新时间:10/11/2023 访问量:69
CPU 中的哪个执行单元执行预取指令?
Which execution unit in the CPU executes the prefetch instruction?
问:
根据Intel的手册,预取指令一般不会触发故障或异常,这与常规的加载指令不同。
PREFETCH 为硬件提供提示;除了少数特殊情况外,它不会生成异常或错误(参见第 9.3.3 节 “预取和加载指令”)。但是,过度使用 PREFETCH 指令可能会浪费内存带宽,并导致资源限制导致性能下降。
考虑到 CPU 微架构中的不同端口用于处理不同类型的指令,(软件)预取和加载指令在 μop 调度和执行阶段是否也使用相同的端口?
硬件预取怎么样?
答:
硬件预取不使用执行单元;例如,单独的硬件可以生成额外的请求,将数据拉入 L2 或 L1d。在 Intel CPU 中,最重要的预取器是 L2 流处理器,它监视对 L2 缓存的访问模式。不是通过将要执行的 uop 馈送到指令流中。类似于 page-walker 如何与负载执行单元并行生成对 L1d 的额外加载请求。(可能与他们争夺有限的缓存读取端口。端口与多端口SRAM不同,而不是执行端口/单元中的端口。
软件预取在负载执行单元上运行。与正常(需求)负载的任何差异都由负载执行单元中的微小额外功能处理。
它需要地址生成,并且需要几乎像负载一样工作,即使对于 x86 或等效版本(写入意图,即预取到 MESI 独占状态,并具有 Read For Ownership。如果缓存中不存在,这仍然是一个负载。但与需求加载不同的是,如果所有缓冲区都已满以跟踪新的缓存行,它可以放弃并且不执行任何操作,因为这只是一个提示。prefetchw
如果任何 ISA 的任何微架构对 SW 预取与负载具有不同的执行单元,我会感到惊讶。(除非 ISA 与 x86 和 AArch64 等主流 ISA 有很大不同。权衡是为负载单元增加一点灵活性,而不是添加一个全新的单元:仅预取执行单元将具有负载单元所需的大部分内容(包括在生成请求之前探测 L1d 标签以查看数据是否已经存在、地址生成和 TLB 读取端口)。此外,还可以窥探存储缓冲区,并检查已在运行的缓存行,这样您就不会为已经传输的行耗尽另一个缓冲区。如果你打算这样做,让它也能够运行正常负载会更有用,而不是试图共享较少数量的 TLB 和缓存标签读取端口,或者更糟糕的是拥有更多端口但通常不会全部使用它们。(或者我想你可以把它放在同一个执行端口/管道上作为负载单元,这样它们就不能在同一周期内启动一个uop,但你仍然会复制很多功能,并且需要管理缓存和TLB等读取端口的共享。
没有错
不出错实际上非常容易:正常需求负载只有在达到退休状态时才会出错(变得非投机性)。处理此问题的正常方法是,跟踪它的 ROB(ReOrder Buffer)条目在停用时被标记为引发异常,作为负载执行单元的一部分,完成执行 uop 并将 ROB 条目标记为完成(准备停用)的工作。因此,每个 ROB 条目已经有一个 fault-or-not 位,或者可能有多个位来确定哪种类型的故障。事实上,故障负载在报废之前不会做任何特别的事情,这是 Meltdown 的关键之一(以及转发到依赖 uops 的实际数据,这些数据永远不会在架构上可见......除非通过定时侧通道。有关推理执行工作原理的更多详细信息,请参阅无序执行与推理执行。据我所知,所有无序的执行 CPU 都使用相同的策略,即在报废之前不对错误指令做任何事情;是否容易受到 Meltdown 的影响取决于依赖的 UOP 是否在故障负载后执行,如果是,它们会看到哪些数据。(例如,always-zero 就可以了。
软件预取指令根本不会在 ROB 条目中设置停用时故障位,而不管寻页的地址或结果如何。(即使是非规范地址在 x86-64 上也不会出错。它们只是提示,因此,如果两个寻页单元都已忙于 TLB-miss 预取,CPU 可能会选择不等待翻页。
相同的停用时故障机制用于使所有指令的推测执行成为可能,包括其他可能因其他原因而出错的指令,以及正常的需求负载。(较旧的 CPU 过去以同样的方式处理分支,只是在报废时从错误预测中恢复,但分支是特殊的,真正的程序在快速路径上确实有分支错误预测,因此额外的硬件(“分支顺序缓冲区”)支持“快速恢复”,从第一次检测到错误预测时开始。div
诀窍在于,在停用之前,每条指令都被视为投机性指令,无论最近是否执行了任何分支或可能出错的指令。可能出错或错误预测的指令在实际代码中太常见了,无法针对任何其他情况进行优化。
唯一没有解决的问题是存储:错误推测的存储数据不能对其他内核可见。通常,这意味着我们不能让它们直接写入 L1d 缓存。存储缓冲区是该问题的解决方案:推测执行的 CPU 分支是否包含访问 RAM 的操作码?
不阻止缓存未命中时停用
在 x86 上,正常负载必须完全完成(数据甚至在缓存未命中时到达),然后才能停用。这是大多数 x86 CPU 保持强内存排序的一部分,例如 LoadLoad 和 LoadStore,这与弱排序的 ISA 不同,在弱排序的 ISA 中,只要已知负载是非故障的,负载就可以完全停用,使用其他(更便宜的)机制来跟踪寄存器结果正在等待缓存行到达的事实,也许就像有序的 CPU 可以和做的那样。
但是预取指令不需要等待数据;一旦他们将缓存行的请求馈送到行填充缓冲区(以跟踪 L1d 正在等待的缓存行),他们就可以在 ROB 中将 uop 标记为准备停用。(即使对于正常负载,uop 也可以在首次调度时离开调度程序,即使导致缓存未命中。
因此,x86 负载执行单元也需要支持这种不同的行为。在弱有序的 ISA 上,就 ROB 而言,即使是需求负载也可能更加火力和遗忘,我假设只有负载缓冲区条目监视数据到达并向相关 uops 发出信号,表明其输入已准备就绪。
但是他们已经需要处理很多情况,例如 TLB 未命中、L2 TLB 未命中触发页面遍历、最终页面错误、缓存行拆分,甚至页面拆分需要稍后在同一端口上再循环,在允许未对齐负载的 ISA 上。以及处理不可缓存(可能是 MMIO)与可缓存的负载,例如 x86 MTRR 或 PAT(SW 预取的工作方式类似于字节加载,因此不必担心拆分。
因此,与加载端口的复杂性相比,支持 SW 预取所需的少量修改非常小,而且它们很少使用,以至于使用另一个完整的执行单元进行预取是没有意义的。
对于 x86,您可以检查 https://uops.info/ 并查看它们使用 Intel CPU 上的加载端口。(例如Skylake/Ice Lake的端口2或3,或Alder Lake的端口2/3 / A)。对于大多数非 SIMD 指令,uops.info 实际上没有 AMD 的执行端口详细信息,但那里也是一样的。
评论