软件预取是否分配了行填充缓冲区(LFB)?

Nat*_*urz 20 64-bit assembly caching bandwidth prefetch

我已经意识到Little's Law限制了在给定的延迟和给定的并发级别下数据传输的速度.如果您想更快地传输某些内容,则需要更大的传输,更多的"飞行中"传输或更低的延迟.对于从RAM读取的情况,并发性受到行填充缓冲区数量的限制.

当加载错过L1缓存时,将分配行填充缓冲区.现代英特尔芯片(Nehalem,Sandy Bridge,Ivy Bridge,Haswell)每个核心有10个LFB,因此每个核心限制为10个未完成的缓存未命中.如果RAM延迟为70 ns(似乎合理),并且每次传输为128字节(64B高速缓存线加上其硬件预取双线),则将每个内核的带宽限制为:10*128B/75 ns = ~16 GB/s.诸如单线程Stream之类的基准确认这是相当准确的.

减少延迟的显而易见的方法是使用x64指令(如PREFETCHT0,PREFETCHT1,PREFETCHT2或PREFETCHNTA)预取所需数据,这样就不必从RAM中读取数据.但是我无法通过使用它们加快速度.问题似乎是__mm_prefetch()指令本身消耗LFB,因此它们也受到相同的限制.硬件预取不会触及LFB,也不会跨越页面边界.

但我无法在任何地方找到任何记录.我发现的最接近的是15年前的文章,其中提到Pentium III上的预取使用Line Fill Buffers.我担心事情可能会发生变化.由于我认为LFB与L1缓存相关联,我不确定为什么L2或L3的预取会消耗它们.然而,我测量的速度与这种情况一致.

那么:有没有办法在没有使用这10个线路填充缓冲器中的一个的情况下从存储器中的新位置开始取出,从而通过绕过Little's定律实现更高的带宽?

Lee*_*eor 10

首先是一个小修正 - 阅读优化指南,你会注意到一些硬件预取程序属于L2高速缓存,因此不受填充缓冲区数量的限制,而是受L2对应物的限制.

"空间预取器"(你所说的共线64B线,完成128B块)是其中之一,所以从理论上讲,如果你获取其他所有线路,你将能够获得更高的带宽(一些DCU预取器可能会尝试"为你填补空白",但从理论上讲,它们应该具有较低的优先级,因此它可能会起作用).

然而,"王"预告是另一个人,"L2流光".第2.1.5.4节内容如下:

Streamer:此预取程序监视来自L1缓存的读取请求,以查找地址的升序和降序序列.受监视的读取请求包括由加载和存储操作以及硬件预取程序发起的L1 DCache请求,以及用于代码获取的L1 ICache请求.当检测到前向或后向请求流时,预取了预期的高速缓存行.预取的缓存行必须位于同一4K页面中

重要的是 -

流传输器可以在每次L2查找时发出两个预取请求.在加载请求之前,流转化器最多可以运行20行

这个2:1比率意味着对于此预取程序识别的访问流,它将始终在您的访问之前运行.确实,你不会自动在L1中看到这些行,但它确实意味着如果一切正常,你应该总是得到它们的L2命中延迟(一旦预取流有足够的时间提前运行并缓解L3 /内存延迟).您可能只有10个LFB,但正如您在计算中所述 - 访问延迟时间越短,您可以越快地获得更高的带宽.这基本上是将L1 <-- mem延迟分解为L1 <-- L2和的并行流L2 <-- mem.

至于标题中的问题 - 按理说,尝试填充L1的预取将需要行填充缓冲区来保存该级别的检索数据.这应该包括所有L1预取.至于SW预取,第7.4.3节说:

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

  • PREFETCH导致DTLB(数据转换旁视缓冲区)未命中.这适用于具有与系列15,型号0,1或2相对应的CPUID签名的奔腾4处理器.PREFETCH解决了Pentium 4处理器上的DTLB未命中和数据,其CPUID签名对应于15系列,型号3.
  • 访问导致错误/异常的指定地址.
  • 如果内存子系统耗尽了第一级缓存和第二级缓存之间的请求缓冲区.

...

所以我认为你是对的,SW预取不是人为增加你未完成请求数量的方法.但是,同样的解释也适用于此 - 如果您知道如何使用SW预取来提前充分访问您的线路,您可以减轻一些访问延迟并提高有效BW.然而,这对长流不起作用有两个原因:1)您的缓存容量有限(即使预取是暂时的,如t0风格),2)您仍然需要支付完整的L1 - > mem延迟每个预取,所以你只是稍微提前一点压力 - 如果你的数据操作比内存访问快,你最终会赶上你的SW预取.所以这只有在你能提前预取所有你需要的东西并将其保留在那里时才有效.


Bee*_*ope 8

根据我的测试,所有类型的预取指令都会消耗最新Intel主流CPU上的行填充缓冲区.

特别是,我向uarch-bench添加了一些加载和预取测试,这些测试在各种大小的缓冲区上使用大跨度负载.以下是我的Skylake i7-6700HQ的典型结果:

                     Benchmark   Cycles    Nanos
  16-KiB parallel        loads     0.50     0.19
  16-KiB parallel   prefetcht0     0.50     0.19
  16-KiB parallel   prefetcht1     1.15     0.44
  16-KiB parallel   prefetcht2     1.24     0.48
  16-KiB parallel prefetchtnta     0.50     0.19

  32-KiB parallel        loads     0.50     0.19
  32-KiB parallel   prefetcht0     0.50     0.19
  32-KiB parallel   prefetcht1     1.28     0.49
  32-KiB parallel   prefetcht2     1.28     0.49
  32-KiB parallel prefetchtnta     0.50     0.19

 128-KiB parallel        loads     1.00     0.39
 128-KiB parallel   prefetcht0     2.00     0.77
 128-KiB parallel   prefetcht1     1.31     0.50
 128-KiB parallel   prefetcht2     1.31     0.50
 128-KiB parallel prefetchtnta     4.10     1.58

 256-KiB parallel        loads     1.00     0.39
 256-KiB parallel   prefetcht0     2.00     0.77
 256-KiB parallel   prefetcht1     1.31     0.50
 256-KiB parallel   prefetcht2     1.31     0.50
 256-KiB parallel prefetchtnta     4.10     1.58

 512-KiB parallel        loads     4.09     1.58
 512-KiB parallel   prefetcht0     4.12     1.59
 512-KiB parallel   prefetcht1     3.80     1.46
 512-KiB parallel   prefetcht2     3.80     1.46
 512-KiB parallel prefetchtnta     4.10     1.58

2048-KiB parallel        loads     4.09     1.58
2048-KiB parallel   prefetcht0     4.12     1.59
2048-KiB parallel   prefetcht1     3.80     1.46
2048-KiB parallel   prefetcht2     3.80     1.46
2048-KiB parallel prefetchtnta    16.54     6.38
Run Code Online (Sandbox Code Playgroud)

需要注意的关键是,任何预取技术都不比任何缓冲区大小的加载快得多.如果任何预取指令没有使用LFB,我们可以预期它对于适合其预取的高速缓存级别的基准测试来说非常快.例如,prefetcht1将行引入L2,因此对于128-KiB测试,如果它不使用LFB,我们可能期望它比加载变量更快.

更确切地说,我们可以检查l1d_pend_miss.fb_full计数器,其描述如下:

请求需要FB(填充缓冲区)条目但没有可用条目的次数.请求包括可缓存/不可缓存的需求,这些需求是加载,存储或SW预取指令.

描述已经表明SW预取需要LFB条目并且测试证实了它:对于所有类型的预取,对于并发是限制因素的任何测试,这个数字非常高.例如,对于512-KiB prefetcht1测试:

 Performance counter stats for './uarch-bench --test-name 512-KiB parallel   prefetcht1':

        38,345,242      branches                                                    
     1,074,657,384      cycles                                                      
       284,646,019      mem_inst_retired.all_loads                                   
     1,677,347,358      l1d_pend_miss.fb_full                  
Run Code Online (Sandbox Code Playgroud)

fb_full值大于循环次数,这意味着LFB几乎一直都是满的(它可能超过循环次数,因为最多两次加载可能需要每个循环一次LFB).这个工作量是纯粹的预取,因此除了预取之外没有什么可以填充LFB.

该测试的结果还在Leeor引用的手册部分中描述了所声称的行为:

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

  • ...
  • 如果内存子系统耗尽了第一级缓存和第二级缓存之间的请求缓冲区.

很明显,这不是这里的情况:当LFB填满时,预取请求不会被丢弃,但是在资源可用之前就像正常负载一样停止(这不是一个不合理的行为:如果你要求软件预取,你可能想要得到它,也许即使它意味着停滞).

我们还注意到以下有趣的行为:

  • 似乎两者之间存在一些细微差别prefetcht1,prefetcht2因为他们报告了16-KiB测试的不同性能(差异各不相同,但总是不同),但如果重复测试,您会发现这更可能只是运行 -由于这些特定值有些不稳定(大多数其他值非常稳定),因此运行变化.
  • 对于包含L2的测试,我们可以在每个周期维持1个负载,但只能prefetcht0预留一个.这有点奇怪,因为它prefetcht0应该与负载非常相似(并且在L1情况下它可以在每个周期发出2个).
  • 即使L2具有~12个周期延迟,我们也能够完全隐藏仅有10个LFB的延迟LFB:我们每个负载获得1.0个周期(受L2吞吐量限制),而不是12 / 10 == 1.2我们期望的每个负载周期(最佳情况)如果LFB是限制性事实(并且非常低的值fb_full确认它).这可能是因为12周期延迟是执行核心的完全负载到使用延迟,其中还包括几个额外延迟周期(例如,L1延迟为4-5个周期),因此实际花费的时间是LFB少于10个周期.
  • 对于L3测试,我们看到3.8-4.1个周期的值,非常接近基于L3负载 - 使用延迟的预期42/10 = 4.2个周期.因此,当我们达到L3时,我们肯定受到10个LFB的限制.这里prefetcht1并且prefetcht2始终比负载或速度快0.3个周期prefetcht0.给定10个LFB,相当于3个周期的占用率,或多或少地解释为预取在L2停止而不是一直到L1.
  • prefetchtnta通常比L1以外的其他产品具有更低的吞吐量.这可能意味着它prefetchtnta实际上正在做它应该做的事情,并且似乎将线路引入L1,而不是L2,而只是"弱"进入L3.因此,对于包含L2的测试,它具有并发限制的吞吐量,就好像它正在访问L3缓存一样,而对于2048-KiB情况(L3缓存大小的1/3),它具有击中主存储器的性能. prefetchnta限制L3缓存污染(每组只有一种方式),所以我们似乎正在逐渐被驱逐.

可能会有所不同吗?

这是我在测试之前写的一个较旧的答案,推测它是如何工作的:

通常,我希望任何导致数据在L1中结束的预取消耗线填充缓冲区,因为我认为L1和内存层次结构的其余部分之间的唯一路径是LFB 1.因此,针对L1的SW和HW预取可能使用LFB.

然而,这留下了以L2或更高级别为目标的预取不消耗LFB的可能性.对于硬件预取的情况,我很确定是这种情况:您可以找到许多参考,解释说HW预取是一种机制,可以有效地获得超出LFB提供的最大值10的内存并行性.此外,如果他们想要的话,L2预取器似乎不能使用LFB:它​​们位于L2中​​/附近并向更高级别发出请求,可能使用超级队列而不需要LFB.

这留下了针对L2(或更高)的软件预取,例如2prefetcht1prefetcht22.与L2生成的请求不同,这些请求从核心开始,因此它们需要某种方式从核心中获取,这可能是通过LFB.从英特尔优化指南中有以下有趣的引用(强调我的):

通常,预取到L2中的软件将显示比L1预取更多的好处.将软件预取到L1中将占用关键硬件资源(填充缓冲区),直到完成高速缓存行填充.L2中的软件预取不会保留这些资源,并且不太可能产生负面的性能影响.如果使用L1软件预取,最好是通过L2高速缓存中的命中来提供软件预取,因此最小化硬件资源保留的时间长度.

这似乎表明软件预取不消耗LFB - 但这个引用仅适用于Knights Landing架构,我找不到任何更主流架构的类似语言.似乎Knights Landing的缓存设计明显不同(或引用错误).


1事实上,我认为即使是非临时存储也会使用LFB来逃离执行核心 - 但是它们的占用时间很短,因为只要它们到达L2就可以进入超级队列(实际上不会进入L2) )然后释放他们相关的LFB.

2我认为这两个目标都是针对近期英特尔的L2,但这一点也不清楚 - 或许这些t2提示实际上是针对一些uarchs的LLC?