首页 文章

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

提问于
浏览
17

我已经意识到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岁的article,其中提到Pentium III上的预取使用Line Fill Buffers . 我担心事情可能会发生变化 . 而且我认为LFB 's are associated with the L1 cache, I' m不确定为什么对L2或L3的预取会消耗掉它们 . 然而,我测量的速度与这种情况一致 .

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

2 回答

  • 7

    首先是一个小修正 - 读取optimization guide,你会注意到一些硬件预取程序属于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 <-- L2L2 <-- mem 的并行流 .

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

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

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

  • 8

    根据我的测试, all types of prefetch instructions consume line fill buffers on recent Intel mainstream CPUs .

    特别是I added some load & prefetch tests to uarch-bench,它在各种大小的缓冲区上使用大 Span 负载 . 以下是我的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
    

    需要注意的关键是,任何预取技术都不比任何缓冲区大小的加载快得多 . 如果任何预取指令没有使用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
    

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

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

    有些情况下PREFETCH不会执行数据预取 . 其中包括:...如果内存子系统耗尽了第一级缓存和第二级缓存之间的请求缓冲区 .

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

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

    • 似乎 prefetcht1prefetcht2 之间存在一些细微差别,因为他们报告了16-KiB测试的不同性能(差异有所不同,但总是不同),但如果重复测试,您会发现这更有可能只是逐次运行的变化,因为这些特定值有些不稳定(大多数其他值非常稳定) .

    • 对于包含L2的测试,我们可以在每个周期维持1个负载,但只能有一个 prefetcht0 预取 . 这有点奇怪,因为 prefetcht0 应该与负载非常相似(并且在L1情况下它可以在每个周期发出2个) .

    • 尽管L2有~12个周期延迟,但我们能够完全隐藏只有10个LFB的延迟LFB:我们每个负载获得1.0个周期(受L2吞吐量限制),而不是我们期望的每个负载 12 / 10 == 1.2 个周期(最好) case)如果LFB是限制性事实(并且 fb_full 的非常低的值确认了它) . 这可能是因为12周期延迟是执行核心的完全负载到使用延迟,其中还包括几个额外延迟周期(例如,L1延迟为4-5个周期),因此实际花费的时间是LFB少于10个周期 .

    • 对于L3测试,我们看到3.8-4.1个周期的值,非常接近基于L3负载 - 使用延迟的预期42/10 = 4.2个周期 . 因此,当我们达到L3时,我们肯定受到10个LFB的限制 . 这里 prefetcht1prefetcht2 一致比负载快0.3个循环或 prefetcht0 . 给定10个LFB,相当于3个周期的占用率,或多或少地解释为预取在L2停止而不是一直到L1 .

    • prefetchtnta 通常比L1以外的其他产品具有更低的吞吐量 . 这可能意味着 prefetchtnta 实际上正在做它应该做的事情,并且似乎将线路引入L1,而不是L2,而只有"weakly"进入L3 . 因此,对于包含L2的测试,它具有并发限制的吞吐量,就好像它正在访问L3缓存一样,而对于2048-KiB情况(L3缓存大小的1/3),它具有击中主存储器的性能 . prefetchnta limits L3 cache pollution (to something like only one way per set),所以我们似乎正在逐渐被驱逐 .

    可能有所不同吗?

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

    一般来说,我希望任何导致数据在L1中结束的预取消耗行填充缓冲区,因为我相信L1和其余内存层次结构之间的唯一路径是LFB1 . 因此,针对L1的SW和HW预取可能都使用LFB .

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

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

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

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


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

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

相关问题