预取指令是否需要在退出之前返回结果?

Bee*_*ope 6 performance x86 prefetch

在最近的Intel和AMD CPU上,执行的预取指令是否仍然可以退出,但是请求的行还没有到达指定的缓存级别?

也就是说,预取"阻塞"的退出是因为它似乎是负载,还是非阻塞?

Had*_*ais 5

关于英特尔处理器,没有.英特尔优化手册第7.3.3节中提到了这一点:

PREFETCH可以提供比预加载更高的性能,因为:

  • 没有目标寄存器,它只更新缓存行.
  • 如果这会导致错误,则不会完成自己的执行.
  • 不会拖延正常的退休指令.
  • 不影响程序的功能行为.
  • 没有缓存行拆分访问.
  • 除非使用LOCK前缀,否则不会导致异常.LOCK前缀不是与PREFETCH一起使用的有效前缀.
  • 如果这会导致错误,则不会完成自己的执行.

PREFETCH优于预加载指令的优点是处理器特定的.这可能在将来发生变化.

另外第3.7.1节说:

软件PREFETCH操作的工作方式与从内存操作加载的方式相同,但以下情况除外:

  • 软件PREFETCH指令在虚拟到物理地址转换完成后退出.
  • 如果需要预取数据等异常(例如页面错误),则软件预取指令将在不预取数据的情况下退出.

我已经在Haswell和Broadwell上通过实验验证了这两点.

在此输入图像描述

在此输入图像描述

全部错过TLB:所有预取指令都错过了所有MMU和数据缓存,但页面在主存中(没有次要或主要页面错误).

全部命中TLB:所有预取指令都命中L1 TLB和数据缓存.

故障不同页面:所有预取指令都错过了所有MMU和数据高速缓存,页面描述符导致页面错误.每个预取指令访问不同的虚拟页面.

故障相同页面:所有预取指令都错过了所有MMU和数据高速缓存,页面描述符导致页面错误.每个预取指令访问同一个虚拟页面.

对于Broadwell微架构图,结果两者PREFETCH0PREFETCHW被示出.PREFETCHWHaswell不支持.Haswell和Broadwell的频率分别固定在3.4GHz和1.7GHz,我在两者上都使用了intel_pstate功率缩放驱动器.所有硬件预取程序都已打开.请注意,PREFETCHW页面错误的延迟与目标页面是否可写无关.由于任何其他原因,只读页面会导致与故障具有相同影响的故障.此外,我的实验只考虑没有核心具有缓存行副本的情况.

由于1c依赖关系链,预计1个周期的吞吐量:

loop:
prefetcht0 (%rax)
add    $0x1000,%rax 
cmp    %rbx,%rax
jne    loop
Run Code Online (Sandbox Code Playgroud)

在Broadwell上,"故障相同页面"的情况似乎比"故障不同页面"情况略慢.这与Haswell形成鲜明对比.我不知道为什么.这可能取决于包含无效条目的分页结构的级别,在该条目基本上页面遍历器检测到页面错误.这取决于操作系统.

我认为预取指令不能在TLB未命中时立即退出的原因是因为加载单元没有像存储单元那样的退休后逻辑.这里的想法是,由于很可能在预取之后会有对页面的请求访问(这可以预测为什么预取在那里),因此无论如何在请求访问或预取时都会由于TLB未命中而停止.当紧接预取之后的指令不访问同一页面时,可能更好地停止预取.

另外,我已经通过实验验证了预取指令可以在预取操作完成之前通过放置LFENCE预取指令并观察每个预取指令的时间仅略微增加(栅栏的成本)与使用负载而不是预取.

Xeon Phi处理器上的软件预取指令的执行方式与Haswell/Broadwell 1相同,但请阅读下面的Itanium部分.

第7.3.3节还说:

有些情况下PREFETCH不会执行数据预取.这些包括:

  • 在较旧的微体系结构中,导致数据转换旁视缓冲区(DTLB)未命中的PREFETCH将被丢弃.在基于Nehalem,Westmere,Sandy Bridge和更新的微架构,Intel Core 2处理器和Intel Atom处理器的处理器中,可以跨页面边界获取导致DTLB未命中的PREFETCH.
  • 访问导致错误/异常的指定地址.
  • PREFETCH以无法缓存的内存区域为目标(例如,USWC和UC).
  • 如果内存子系统耗尽了第一级缓存和第二级缓存之间的请求缓冲区.
  • 使用LOCK前缀.这会导致无效的操作码异常.

第二点已经在Haswell,Broadwell和Skylake上通过实验验证.我的代码无法检测到第四个点,即当用完LFB时,可以删除状态预取请求.在AMD结果似乎表明,AMD也下降预取请求.但AMD的每次访问时间仍远小于英特尔.我认为当TLB填充缓冲区已满时,AMD会删除预取请求,而当L1D填充缓冲区已满时,Intel会丢弃它们.我的代码永远不会使L1D填充缓冲区满,这解释了AMD与英特尔的结果.

第一点说,在较旧的处理器上,TLB未命中时会丢弃软件预取,但从第二代Core和Atom开始,这已经发生了变化.这些旧处理器包括:Pentium III,Pentium 4,Pentium M和1st-gen Core.Pentium III之前的处理器不支持软件预取指令.


预取uops被分派到端口2或3并在加载缓冲区中分配.预取uop到同一个缓存行不会合并.也就是说,每个uop都会获得自己的加载缓冲区.我认为(但我没有通过实验验证)ROB条目被分配用于预取uops.只要它们被分派到一个加载端口,它就只能在预取uops上停止.

预取请求本身(发送到L1d或外层高速缓存)不是预取uop在ROB中标记为完成并准备退出之前必须等待的东西,这与常规负载不同.


2011年有一项有趣的专利讨论了对Itanium 2处理器上软件预取的增强.它提到以前的Itanium处理器在软件预取错过TLB时必须停止,因为它们被设计为不丢弃任何软件预取请求,后来的指令无法通过它,因为它们是有序处理器.该专利提出了一种设计,允许软件预取请求相对于后续指令无序执行而不丢弃它们.这是通过添加数据预取队列(DPQ)来完成的,该队列用于排队错过TLB的软件预取请求.然后在硬件页表行走完成后重新发出DPQ中的预取.此外,添加了多个硬件页面表助行程序,即使它们错过了TLB,也可能允许以后的请求访问执行.但是,如果DPQ填满预取指令,则管道在下一个预取指令上停止.同样根据该专利,即使在页面错误上也不会丢弃软件预取请求.这与大核心和Xeon Phi形成鲜明对比.该专利还讨论了在Itanium中实现的硬件预取程序.

在无序的大核微体系结构中,加载缓冲区自然地扮演着DPQ的角色.我不知道Xeon Phi是否有这样的结构.


AMD优化手册第5.6节说明如下:

预取指令可能受到对存储的错误依赖性的影响.如果存储到与请求匹配的地址,则可以阻止该请求(预取指令),直到将存储写入高速缓存.因此,代码应该预取至少与周围存储的数据地址相距64字节的数据.

我很好奇,通过将两个预取指令和一个存储指令(后面跟一个虚拟测试这对Intel处理器(上的Haswell) add rax, rax),以及我观察到以下几点:

  • UOPS_RETIRED.STALL_CYCLES 显着大于核心循环计数,这没有任何意义.
  • 调度到端口2和3的uop总数比预期的高出约16%.这表示正在重播预取uops.
  • RESOURCE_STALLS.ANY报告基本上没有摊位.这与两个预取指令后跟两个伪ALU指令(加载缓冲器上的流水线停止)的情况形成对比.

但是,只有当商店与预取指令位于同一个4K页面时,才会观察到这些效果.如果商店位于不同的页面,则代码的工作方式类似于具有两个虚拟ALU的代码.因此,商店似乎与英特尔处理器上的预取指令进行交互.


(1)但它们与硬件预取器的交互方式不同.但是,这是退休后的影响.

(2)Itanium是一系列IA-64处理器,因此它与问题并不完全相关.