我已经意识到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与L1缓存相关联,我不确定为什么L2或L3的预取会消耗它们。然而,我测量的速度与这种情况一致。
那么:有没有办法从内存中的新位置开始提取而不使用这10个线路填充缓冲器中的一个,从而通过避开Little's定律来实现更高的带宽?
答案 0 :(得分:9)
首先是一个小修正 - 阅读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 <-- L2
和L2 <-- mem
的并行流。
至于标题中的问题 - 按理说,尝试填充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 - &gt; mem延迟对于每个预取,所以你只是稍微向前移动压力 - 如果你的数据操作比内存访问快,你最终会赶上你的SW预取。所以这只有在你能提前预取所有你需要的东西并将其保留在那里时才有效。
答案 1 :(得分:8)
根据我的测试,所有类型的预取指令都会消耗最新Intel主流CPU上的行填充缓冲区。
特别是I added some load & prefetch tests to 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
需要注意的关键是,任何预取技术都不比任何缓冲区大小的加载快得多。如果任何预取指令没有使用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填满时,预取请求不会被丢弃,但是在资源可用之前就像正常负载一样停止(这不是一种不合理的行为:如果你要求软件预取,你可能想得到它,也许即使它意味着停滞)。
我们还注意到以下有趣的行为:
prefetcht1
和prefetcht2
之间存在一些细微差别,因为它们报告了16-KiB测试的不同表现(差异因人而异,但总是不同),但如果你重复测试你会发现这更可能只是逐次运行的变化,因为这些特定值有些不稳定(大多数其他值非常稳定)。prefetcht0
预取。这有点奇怪,因为prefetcht0
应该与负载非常相似(并且在L1情况下它可以在每个周期发出2个)。 12 / 10 == 1.2
周期如果LFB是限制性事实(fb_full
的非常低的值确认它),我们期望(最佳情况)。这可能是因为12周期延迟是执行核心的完全负载到使用延迟,其中还包括几个额外延迟周期(例如,L1延迟为4-5个周期),因此在LFB中花费的实际时间少于10个周期。prefetcht1
和prefetcht2
始终比加载或prefetcht0
快0.3个周期。给定10个LFB,相当于3个周期的占用率,或多或少地解释为预取在L2停止而不是一直到L1。prefetchtnta
通常比L1以外的其他人具有更低的吞吐量。这可能意味着prefetchtnta
实际上正在做它应该做的事情,并且似乎将线条带入L1,而不是带入L2,并且只有&#34;弱的&#34;进入L3。因此,对于包含L2的测试,它具有并发限制的吞吐量,就好像它正在访问L3缓存一样,而对于2048-KiB情况(L3缓存大小的1/3),它具有击中主存储器的性能。 prefetchnta
limits L3 cache pollution (to something like only one way per set),所以我们似乎正在逐渐被驱逐。这是我在测试之前写的一个较旧的答案,推测它是如何工作的:
一般情况下,我希望任何导致数据在中结束的预取使用行填充缓冲区,因为我相信L1和内存层次结构的其余部分之间的唯一路径是LFB 1 。因此,针对L1的SW和HW预取可能都使用LFB。
然而,这使得针对L2或更高级别的预取不会消耗LFB的可能性更大。对于硬件预取的情况,我非常肯定是这种情况:您可以找到许多参考,解释说HW预取是一种机制,可以有效地获得超出LFB提供的最大值10的内存并行性。此外,如果他们想要的话,L2预取者似乎不能使用LFB:他们居住在L2附近,并向更高级别发出请求,大概是使用超级队列,并且不需要LFB。 / p>
这使得软件预取以L2(或更高)为目标,例如prefetcht1
和prefetcht2
2 。与L2生成的请求不同,这些请求从核心开始,因此它们需要某种方式从核心中获取,这可能是通过LFB。从英特尔优化指南中得到以下有趣的引用(强调我的):
通常,预取到L2的软件会显示出更多的好处 比L1预取。对L1的软件预取将消耗关键 硬件资源(填充缓冲区),直到缓存行填充完成。 :一种 软件预取到L2中并不能保存这些资源,而且确实如此 不太可能产生负面的绩效影响。如果你使用L1 软件预取,最好是软件预取服务 通过L2缓存中的命中,所以硬件的时间长度 资源被最小化。
这似乎表明软件预取不会消耗LFB - 但这个引用仅适用于Knights Landing架构,我找不到任何更主流架构的类似语言。似乎Knights Landing的缓存设计明显不同(或引用错误)。
1 事实上,我认为即使是非临时商店也会使用LFB来摆脱执行核心 - 但是他们的占用时间很短,因为只要他们到达L2就可以了可以进入超级队列(实际上没有进入L2),然后释放他们相关的LFB。
2 我认为这两个目标都是针对近期英特尔的L2,但这也不清楚 - 也许t2
暗示实际上是针对某些uarchs的LLC?