关于英特尔处理器,没有。这在英特尔优化手册第 7.3.3 节中有所提及:
PREFETCH 可以提供比预加载更好的性能,因为:
- 没有目标寄存器,它只更新缓存行。
- 如果这会导致错误,则不会完成自己的执行。
- 不会停止正常的指令退出。
- 不影响程序的功能行为。
- 没有缓存行拆分访问。
- 除非使用 LOCK 前缀,否则不会导致异常。 LOCK 前缀不是用于 PREFETCH 的有效前缀。
- 如果这会导致错误,则不会完成自己的执行。
PREFETCH 优于预加载指令的优势是特定于处理器的。这在未来可能会改变。
另外第 3.7.1 节说:
软件 PREFETCH 操作的工作方式与从内存中加载的方式相同
操作,但以下情况除外:
- 软件 PREFETCH 指令在虚拟到物理地址转换完成后退出。
- 如果需要异常(例如页面错误)来预取数据,则软件预取指令将退出而不
预取数据。
我已经在 Haswell 和 Broadwell 上通过实验验证了这两点。
所有未命中 TLB:所有预取指令均未命中所有 MMU 和数据缓存,但该页面位于主内存中(无次要或主要页面错误)。
All hit TLB:所有预取指令都命中 L1 TLB 和数据缓存。
错误的不同页面:所有预取指令都错过了所有 MMU 和数据缓存,并且页面描述符导致页面错误。每条预取指令访问不同的虚拟页面。
故障同一页面:所有预取指令都未命中所有 MMU 和数据缓存,并且页面描述符导致页面故障。每条预取指令都访问同一个虚拟页面。
对于 Broadwell 图,显示了 PREFETCH0 和 PREFETCHW 的结果。 Haswell 不支持PREFETCHW。 Haswell 和 Broadwell 的频率分别固定为 3.4GHz 和 1.7GHz,我在两者上都使用了 intel_pstate 功率调节驱动程序。所有硬件预取器都已打开。请注意,页面错误时PREFETCHW 的延迟与目标页面是否可写无关。只读页面导致的故障与由于任何其他原因导致的故障具有相同的影响。另外,我的实验只考虑了没有内核拥有缓存行副本的情况。
由于 1c 依赖链,预计 1 个周期的吞吐量:
loop:
prefetcht0 (%rax)
add $0x1000,%rax
cmp %rbx,%rax
jne loop
在 Broadwell 上,“故障相同页面”案例似乎比“故障不同页面”案例稍慢。这与哈斯韦尔形成鲜明对比。我不知道为什么。这可能取决于包含无效条目的分页结构的级别,在该级别基本上页面遍历器检测到页面错误。这取决于操作系统。
我认为预取指令不能在 TLB 未命中时立即退出的原因是因为加载单元没有像存储单元那样的退出后逻辑。这里的想法是,由于很可能会在预取之后对页面进行请求访问(这可能是预取存在的原因),因此无论是请求访问还是预取,都会由于 TLB 未命中而出现停顿。尤其是当紧跟在预取之后的指令不访问同一页面时,暂停预取可能会更好。
此外,我已经通过实验验证了预取指令可以在预取操作完成之前退出,方法是在预取指令之后放置一个LFENCE,并观察到每条预取指令的时间仅略有增加(栅栏的成本)与使用加载而不是预取相比。
Xeon Phi 处理器上的软件预取指令的执行方式与 Haswell/Broadwell 1 上的相同,但另请阅读下面有关 Itanium 的部分。
第 7.3.3 节还说:
在某些情况下,PREFETCH 不会执行数据预取。
其中包括:
- 在较旧的微体系结构中,会丢弃导致数据转换后备缓冲区 (DTLB) 未命中的 PREFETCH。在基于处理器的
Nehalem、Westmere、Sandy Bridge 和更新的微架构,英特尔
Core 2 处理器和 Intel Atom 处理器,PREFETCH 导致 DTLB
可以跨页面边界获取未命中。
- 对导致故障/异常的指定地址的访问。
- PREFETCH 以不可缓存的内存区域(例如,USWC 和 UC)为目标。
- 如果内存子系统耗尽了一级缓存和二级缓存之间的请求缓冲区。
- 使用了 LOCK 前缀。这会导致无效的操作码异常。
第二点已在 Haswell、Broadwell 和 Skylake 上进行了实验验证。我的代码无法检测到在 LFB 用完时可以丢弃状态预取请求的第四点。 AMD results 似乎表明 AMD 也丢弃了预取请求。但是 AMD 的每次访问时间仍然比 Intel 的要小得多。我认为当 TLB 填充缓冲区已满时,AMD 会丢弃预取请求,而当 L1D 填充缓冲区已满时,英特尔会丢弃它们。我的代码永远不会使 L1D 填充缓冲区满,这解释了 AMD 与 Intel 的结果。
第一点说,在 Core2 和 Atom 微架构及更高版本上,软件预取不会因 TLB 未命中而丢弃。根据旧版本的优化手册,型号为 3 或更大的 Pentium 4 处理器也不会在 TLB 未命中时丢弃软件预取。英特尔酷睿微架构和(某些)奔腾 M 处理器也可能是这种情况(我无法找到有关这些处理器的英特尔资源)。型号小于 3 的 Pentium III 处理器和 Pentium 4 处理器肯定会在 TLB 未命中时放弃软件预取。 Pentium III 之前的处理器不支持软件预取指令。
预取微指令被分派到端口 2 或 3 并分配在加载缓冲区中。不会合并到同一缓存行的预取 uops。也就是说,每个 uop 都会获得自己的加载缓冲区。我认为(但我没有通过实验验证)ROB 条目是为预取微指令分配的。只是 ROB 永远不会在预取微指令上停止,只要它们已被分派到加载端口。
与常规加载不同,预取请求本身(发送到 L1d 或外层缓存)不是预取 uop 在被标记为 ROB 中的完成并准备退出之前必须等待的东西。
2011 年有一个有趣的patent 讨论了对 Itanium2 处理器上软件预取的增强。它提到以前的 Itanium 处理器在软件预取错过 TLB 时必须停止,因为它们被设计为不会丢弃任何软件预取请求,并且后来的指令无法通过它,因为它们是有序处理器。该专利提出了一种设计,允许软件预取请求相对于后面的指令乱序执行,而不会丢弃它们。这是通过添加一个数据预取队列 (DPQ) 来完成的,该队列用于对错过 TLB 的软件预取请求进行排队。然后在硬件页表遍历完成后重新发出 DPQ 中的预取。此外,添加了多个硬件页表遍历器,以潜在地允许以后的请求访问执行,即使它们错过了 TLB。但是,如果 DPQ 被预取指令填满,流水线会在下一条预取指令上停止。同样根据该专利,即使在页面错误时也不会丢弃软件预取请求。这与大核心和至强融核形成鲜明对比。该专利还讨论了在 Itanium 中实现的硬件预取器。
在乱序的大核微架构中,负载缓冲区自然扮演着 DPQ 的角色。不知道至强融核有没有这样的结构。
AMD 优化手册第 5.6 节说明如下:
预取指令可能会受到错误依赖的影响
商店。如果存在与请求匹配的地址的存储,则
请求(预取指令)可能会被阻塞,直到存储
写入缓存。因此,代码应该预取数据
距离任何周围存储的数据至少 64 字节
地址。
我很好奇,通过放置两条预取指令和一条存储指令(后跟一个虚拟 add rax, rax)在 Intel 处理器(在 Haswell 上)上进行测试,我观察到以下情况:
-
UOPS_RETIRED.STALL_CYCLES 明显大于核心周期数,这是没有意义的。
- 派往端口 2 和 3 的微指令总数比预期的高出约 16%。这表明正在重放预取微指令。
-
RESOURCE_STALLS.ANY 报告基本上没有摊位。这与两个预取指令后跟两个伪 ALU 指令(加载缓冲区上的流水线停顿)的情况形成鲜明对比。
但是,我仅在存储与预取指令位于同一 4K 页面时观察到这些影响。如果存储到不同的页面,则代码的工作方式类似于具有两个虚拟 ALU 的代码。因此,商店似乎与英特尔处理器上的预取指令交互。
(1) 但它们与硬件预取器的交互方式不同。但是,这是退休后的影响。
(2) Itanium 是 IA-64 处理器系列,因此与问题并不完全相关。