关于英特尔处理器,没有。 Intel优化手册第7.3.3节提到了这一点:
PREFETCH 可以提供比预加载更好的性能,因为:
- 没有目标寄存器,它只更新缓存行。
- 如果这会导致错误,则不会完成自己的执行。
- 不会停止正常指令的报废。
- 不影响程序的功能行为。
- 没有缓存行分割访问。
- 除非使用 LOCK 前缀,否则不会导致异常。 LOCK 前缀不是与 PREFETCH 一起使用的有效前缀。
- 如果这会导致错误,则不会完成自己的执行。
PREFETCH 相对于预加载指令的优点是特定于处理器的。这在未来可能会改变。
另外第 3.7.1 节说:
软件预取操作的工作方式与从内存加载相同
操作,但以下情况除外:
- 软件预取指令在虚拟地址到物理地址转换完成后退出。
- 如果需要预取数据时出现异常(例如缺页错误),则软件预取指令将退出而不需要
预取数据。
我已经在 Haswell 和 Broadwell 上通过实验验证了这两点。
全部未命中 TLB:所有预取指令均未命中所有 MMU 和数据缓存,但页面位于主内存中(没有次要或主要页面错误)。
全部命中TLB:所有预取指令命中L1 TLB和数据缓存。
不同页面出现故障:所有预取指令都会错过所有 MMU 和数据缓存,并且页面描述符会导致页面故障。每个预取指令访问不同的虚拟页。
同一页故障:所有预取指令都会错过所有 MMU 和数据缓存,并且页描述符会导致页故障。每个预取指令访问相同的虚拟页。
对于 Broadwell 图,两者的结果PREFETCH0
and PREFETCHW
显示。PREFETCHW
Haswell 不支持。 Haswell 和 Broadwell 的频率分别固定为 3.4GHz 和 1.7GHz,我在两者上都使用了 intel_pstate 功率缩放驱动程序。所有硬件预取器均已打开。请注意,延迟PREFETCHW
页面错误与目标页面是否可写无关。只读页导致的故障与任何其他原因导致的故障具有相同的影响。另外,我的实验仅考虑没有核心拥有缓存行副本的情况。
由于 1c 依赖链,预计会出现 1 个周期的吞吐量:
loop:
prefetcht0 (%rax)
add $0x1000,%rax
cmp %rbx,%rax
jne loop
在 Broadwell 上,“故障相同页面”情况似乎比“故障不同页面”情况稍微慢一些。这与 Haswell 形成鲜明对比。我不知道为什么。这可能取决于包含无效条目的分页结构的级别,基本上页遍历器在该级别上检测到页面错误。这取决于操作系统。
我认为预取指令不能在 TLB 未命中时立即退出的原因是因为加载单元没有像存储单元那样的退出后逻辑。这里的想法是,由于很可能会在预取之后对页面进行需求访问(这可能是预取存在的原因),因此无论是在需求访问还是在预取上,由于 TLB 未命中都会出现停顿。也许在预取上停止会更好,特别是当预取后紧随其后的指令不访问同一页面时。
此外,我已经通过实验验证了预取指令可以在预取操作完成之前退出通过放置一个LFENCE
在预取指令之后,观察到与使用加载而不是预取相比,每个预取指令的时间仅略有增加(栅栏的成本)。
Software prefetching instructions on Xeon Phi processors are executed the same way as on Haswell/Broadwell 1, but read also the section on Itanium below.
第 7.3.3 节还指出:
在某些情况下,PREFETCH 不会执行数据预取。
这些包括:
- 在较旧的微体系结构中,导致数据转换后备缓冲区 (DTLB) 未命中的 PREFETCH 将被丢弃。在基于处理器
Nehalem、Westmere、Sandy Bridge 和更新的微架构、Intel
Core 2 处理器和 Intel Atom 处理器、PREFETCH 导致 DTLB
miss 可以跨页边界获取。
- 对指定地址的访问导致错误/异常。
- PREFETCH 的目标是不可缓存的内存区域(例如,USWC 和 UC)。
- 如果内存子系统耗尽了一级缓存和二级缓存之间的请求缓冲区。
- 使用 LOCK 前缀。这会导致无效操作码异常。
第二点已经在Haswell、Broadwell、Skylake上得到了实验验证。我的代码无法检测第四点,即当 LFB 耗尽时可以删除预取请求。这AMD 结果 https://travis-ci.org/travisdowns/pf-test/jobs/432012468似乎表明 AMD 也放弃了预取请求。但AMD上的每次访问时间仍然比Intel上的少很多。我认为当 TLB 填充缓冲区已满时,AMD 会丢弃预取请求,而当 L1D 填充缓冲区已满时,Intel 会丢弃预取请求。我的代码从未使 L1D 填充缓冲区填满,这解释了 AMD 与 Intel 的结果。
第一点是说,在 Core2 和 Atom 微架构及更高版本上,软件预取不会因 TLB 未命中而被丢弃。根据旧版本的优化手册,型号为 3 或更大的 Pentium 4 处理器也不会在 TLB 未命中时放弃软件预取。 Intel Core 微架构和(某些)Pentium M 处理器也可能出现这种情况(我无法找到有关这些处理器的 Intel 来源)。型号小于 3 的 Pentium III 处理器和 Pentium 4 处理器在 TLB 未命中时肯定会放弃软件预取。 Pentium III 之前的处理器不支持软件预取指令。
预取微指令被分派到端口 2 或 3 并分配到加载缓冲区中。预取微指令到同一缓存行不会合并。也就是说,每个微指令都会获得自己的加载缓冲区。我认为(但我没有通过实验验证)ROB 条目是为预取微指令分配的。只是 ROB 永远不会在预取微指令上停止,只要它们已被分派到加载端口。
与常规加载不同,预取请求本身(发送到 L1d 或高速缓存的外部级别)不需要在 ROB 中标记为完成并准备退出之前等待预取 uop。
There is an interesting 2011 patent https://patents.google.com/patent/US9442861B2/en that discusses an enhancement to software prefetching on Itanium2 processors. It mentions that previous Itanium processors had to stall when a software prefetch missed the TLB because they were designed to not drop any software prefetch requests and later instructions could not proceed past it because they were in-order processors. The patent proposed a design that allows software prefetching requests to execute out-of-order with respect to later instructions without dropping them. This is done by adding a data prefetch queue (DPQ) which is used to queue up software prefetch requests that miss the TLB. A prefetch in the DPQ is then re-issued after the hardware page table walk completes. In addition, multiple hardware page table walkers are added to potentially allow later demand accesses to execute even if they miss the TLB. However, if the DPQ fills up with prefetch instructions, the pipeline stalls on the next prefetch instruction. Also according to the patent, software prefetch requests are not dropped even on page faults. This is in contrast to big cores and Xeon Phi. The patent also discusses the hardware prefetchers implemented in Itanium.
在乱序大核心微架构中,加载缓冲器自然扮演着 DPQ 的角色。我不知道Xeon Phi是否有这样的结构。
AMD 优化手册第 5.6 节说道:
预取指令可能会受到错误依赖的影响
商店。如果存在与请求匹配的地址的存储,则
请求(预取指令)可能会被阻止,直到存储完成
写入缓存。因此,代码应该预取数据
与任何周围存储的数据至少相距 64 个字节
地址。
我很好奇,想在 Intel 处理器(Haswell 上)上测试这一点,方法是放置两条预取指令和一条存储指令(后跟一个虚拟指令)add rax, rax
),并且我观察到以下情况:
-
UOPS_RETIRED.STALL_CYCLES
明显大于核心周期数,这是没有意义的。
- 发送到端口 2 和 3 的 uop 总数比预期高出约 16%。这表明正在重放预取微指令。
-
RESOURCE_STALLS.ANY
报道称基本没有摊位。这与有两个预取指令后跟两个虚拟 ALU 指令的情况形成对比(管道在加载缓冲区上停顿)。
然而,只有当存储与预取指令相同的 4K 页时,我才观察到这些影响。如果存储到不同的页面,则代码的工作方式与具有两个虚拟 ALU 的代码类似。因此,存储似乎与英特尔处理器上的预取指令进行交互。
(1) 但它们与硬件预取器的交互方式不同。然而,这是退休后的影响。
(2) Itanium 是 IA-64 处理器系列,因此与问题并不完全相关。