非时间相关负载和硬件预取器,它们能一起工作吗?

11
执行一系列连续的_mm_stream_load_si128()调用(MOVNTDQA)时,硬件预取器是否仍会启动,或者应该使用显式软件预取(带有NTA提示),以便在避免缓存污染的同时获得预取益处?
我问这个问题是因为它们的目标似乎对我来说是相互矛盾的。流式加载将跳过缓存获取数据,而预取器则尝试主动将数据预取到缓存中。
当顺序迭代大型数据结构(处理后的数据长时间内不会再次访问)时,避免污染缓存层次结构是有意义的,但我不想因为预取器处于空闲状态而频繁地产生近100个周期的惩罚。
目标架构是Intel SandyBridge。

1
好问题。有一个prefetchnta,但我忘记了关于这种情况的阅读内容。 - Peter Cordes
1
根据一些旧的英特尔文档,非临时负载与正常对齐负载相同,除非内存是不可缓存的。我的个人经验证实它们在普通数据上没有性能差异。但这是在 Nehalem/Sandy Bridge 时代。我不知道 Haswell 或 Skylake 是否有任何变化。 - Mysticial
1
@PeterCordes prefetchnta仅将数据拉入L1缓存,而不是所有缓存。话虽如此,我不知道它如何与硬件预取器交互。在内存访问足够“随机”以使硬件预取器失败但“连续”以使用完整缓存行的情况下(这是许多缓存阻塞优化的情况),我发现在没有超线程的情况下,软件预取可以产生巨大的差异。(约10%)但我没有看到prefetcht0prefetchnta之间有任何可观察的区别。 - Mysticial
1
@Mysticial:最近的英特尔设计中,L3是包容性的,因此可以使用L3标记进行缓存一致性检查。如果一个缓存行存在于L1中但不存在于L3中,则如果另一个核心修改了该缓存行,则可能会变得陈旧,但我认为IA32的缓存一致性模型不允许这样做(因此无法以这种方式实现)。prefetchnta是在多核CPU之前的PIII时代引入的。我不会感到惊讶,如果它与当前设计上的prefetch0完全相同,就像lddqu现在与movdqu相同一样。也许prefetchnta使缓存行更有可能被快速地逐出。 - Peter Cordes
@PeterCordes 感谢您对缓存的深入洞察。我从未从缓存一致性的角度考虑过这个问题。 - Mysticial
@Mysticial:我进行了深入挖掘,并找到足够的材料来发布一个答案。它并不完全与OP相关,并引发了更多问题(特别是关于如何使用prefetchnta将某些内容拉入L1而不是L3,考虑到一致性机制)。 - Peter Cordes
4个回答

16
SSE4.1 NT负载(MOVNTDQA)在当前CPU上仅对WC内存区域有特殊作用。在WB内存中,它们就像普通负载一样,但会多花费一个ALU uop。如果想要最小化正常内存的缓存污染,必须使用NT预取(prefetch)。而这些预取不会触发硬件预取器。我认为这部分是因为硬件预取器无法记住哪些流是NT,哪些是正常的。在Intel CPU上,主预取器(“streamer”)位于L2。但prefetchnta绕过了L2,所以它永远看不到这些预取操作。SW NT预取在调整适当的预取距离方面非常“脆弱”,难以使用并且只适用于一台机器。如果预取太远,数据开始被驱逐,那么它就从L1d中丢失,因为它在需要之前不在L2中。
参见 《每个程序员都应该了解的内存知识》仍然有效吗? - SW预取通常比P4上的HW预取器更少用,但NT预取以最小化污染仍然是只能通过软件实现的。
根据Patrick Fay (Intel) 2011年11月的帖子,"在最新的英特尔处理器上,prefetchnta将把内存中的一行数据带入L1数据缓存(而不是其他缓存级别)。" 他还说你需要确保不要过晚进行预取(硬件预取已经将它拉到所有级别),也不要过早(到达时已被清除)。

如在OP的评论中所讨论的那样,当前的英特尔CPU有一个大型共享的L3缓存,该缓存包含所有每个核心的缓存。这意味着缓存一致性流量只需检查L3标记,以查看某个缓存行是否可能在某个每个核心的L1/L2中被修改。(Skylake及更高版本的Xeon(服务器)核心不再使用包容性L3,而是具有单独的一致性目录或过滤器。)

我不知道如何调和Pat Fay的解释与我的缓存一致性/缓存层次结构理解。我认为如果它进入了L1,它也必须进入L3。也许L1标记有某种标志,表示此线路是弱序的?我最好的猜测是他在简化,说L1时实际上只进入填充缓冲区。我认为那是一种过度简化或仅适用于没有包容性L3的旧CPU(在Nehalem之前)。我认为出于缓存一致性原因,它必须被拉入适当的缓存中。并且没有足够的填充缓冲区来支持有用的预取距离(向前读取足够远)。

BeeOnRope的回答指出,英特尔的优化手册表示NT预取自WB内存会填充L1d缓存,并且(在具有包容式L3缓存的CPU上)集合关联的L3缓存的一条“路”。因此,对于一个巨大的数组进行NT预取只会污染大约1/16的L3。

这个Intel关于视频RAM工作的指南讲述了使用load/store缓冲区而不是缓存行进行非暂态移动。 (请注意,这可能仅适用于不可缓存内存。)它没有提到预取。 它也很老,早于SandyBridge。 但是,它有一个有趣的引用:

普通的load指令按照请求指令的大小从USWC(又名WC)内存中提取数据。相比之下,流式load指令(例如MOVNTDQA)通常会将整个缓存行的数据拉入CPU中的特殊“填充缓冲区”。随后的流式load将从该填充缓冲区读取,产生更少的延迟。

然后在另一段中,它说典型的CPU有8到10个填充缓冲区。 SnB/Haswell每个核心仍然有10个。 再次请注意,这可能仅适用于不可缓存的内存区域。

movntdqa在WB(写回)内存上是不弱序的(请参阅链接答案中的NT加载部分),因此不允许"过时"。与NT存储不同,movntdqaprefetchnta都不会改变写回内存的内存排序语义。

我没有测试这个猜测,但在现代Intel CPU上,prefetchnta/movntdqa可以将缓存行加载到L3和L1中,但可以跳过L2(因为L2既不包含也不排除L1)。 NT提示可能会通过将缓存行放置在其集合的LRU位置来产生影响,从而成为下一个被驱逐的线。 (正常的高速缓存策略将新行插入MRU位置,最远离被驱逐。有关缓存插入策略的更多信息,请参见这篇关于IvB自适应L3策略的文章)。

实际上,它会预取到集合中的一种方式,因此下一个NT预取肯定会驱逐先前的NT预取,而不是其他内容。

IvyBridge上的预取吞吐量仅为43个周期中的一个,因此如果您不希望预取在IvB上减慢代码速度,请小心不要预取过多。来源:Agner Fog的insn表和微架构指南。这是特定于IvB的性能错误。在其他设计中,过多的预取只会占用可以用于有用指令(而非预取无用地址)的uop吞吐量。

关于SW预取一般情况(不是nt类型):Linus Torvalds发帖称它们在Linux内核中很少有帮助,通常会带来更多的问题。显然,在链表末尾预取空指针可能会导致减速,因为它会尝试TLB填充。


2
+1 不错的研究!是的,我完全禁用了针对Ivy Bridge的任何预取。我可以确认,预取null是一个可怕的想法。这是我尝试避免特定函数的“无预取”版本的方法之一。完全行不通。VTune对此提出了批评。 - Mysticial
2
@Leeor:IvB每43个周期只能退役一个prefetch*指令。SnB和Haswell每0.5个周期可以退役一个(它们在加载端口上运行)。因此,过度使用prefetch可能会导致在IvB上prefetch指令本身成为瓶颈,特别是当数据已经在缓存中时。 - Peter Cordes
1
我测试了一组独立预取(L1驻留,以避免内存限制),获得了0.5的吞吐量。我认为我稍后会提出一个关于这个问题的问题,也许我做错了什么。 - Leeor
2
当我在VTune下查看它时,情况1(仅流式加载)显示所有时间都花费在这些加载中。毫不奇怪,它们来自内存。在情况2和3(使用预取),VTune显示所有时间都花费在预取本身上,而在流式加载上没有花费任何时间。这让我感到惊讶,因为它表明有限数量的预取正在飞行,并且当达到限制时它们将阻止执行。如果它们没有阻塞,那么如果内存控制器无法跟上预取请求,惩罚仍然会出现在加载中。 - Mysticial
2
@Mysticial:英特尔的手册暗示prefetchNTA将数据预取到L1D(一级数据缓存),L3(三级缓存)中的一条路,绕过了L2。在SKX上,或许它也会绕过L3,因为现在L3不再是包容性的了(并且只更新某些标记)。可能SKX还通过仅将数据预取到给定集合中的一条路来限制L1D中的污染吗?32/8 = 4,所以如果NT预取仅使用L1D的一条路,则4kiB的大小刚好足够跨越您要访问的数据。 (我不知道这是否是一个可能的设计更改,但可以尝试较小的预取距离)。否则,也许它是某种设计错误... - Peter Cordes
显示剩余11条评论

10

最近在回答另一个问题时,我进行了一些关于不同prefetch类型的测试,并得出以下结论:

使用prefetchnta的结果与Skylake客户端上的以下实现一致:

  • prefetchnta将值加载到L1L3,但不是L2(实际上,如果该行已经在L2中,则似乎该行可能被逐出)。
  • 它似乎以“正常”的方式将值加载到L1中,但在L3中以更弱的方式加载,使其更快地被逐出(例如,只在集合中的单个路线中加载,或者将其LRU标志设置为下一个受害者)。
  • 像所有其他预取指令一样,prefetchnta使用LFB条目,因此它们并不能真正帮助你获得额外的并行性:但NTA提示可以在避免L2和L3污染方面很有用。
当前的优化手册(248966-038)在几个地方声称,prefetchnta确实将数据引入L2,但仅在组中的一个路上。例如,在7.6.2.1视频编码器中:

针对视频编码器实施的预取缓存管理可降低内存访问量。防止单次使用的视频帧数据进入第二级缓存可确保减少第二级缓存的污染。使用非临时PREFETCH(PREFETCHNTA)指令只将数据引入第二级缓存的一个路,从而降低了第二级缓存的污染。

这与我在Skylake上的测试结果不一致。在64 KiB区域内进行跨步操作,并使用prefetchnta,性能表现几乎完全与从L3获取数据相同(每次加载约4个周期,MLP系数为10,L3延迟约40个周期):
                                 Cycles       ns
         64-KiB parallel loads     1.00     0.39
    64-KiB parallel prefetcht0     2.00     0.77
    64-KiB parallel prefetcht1     1.21     0.47
    64-KiB parallel prefetcht2     1.30     0.50
   64-KiB parallel prefetchnta     3.96     1.53

由于Skylake的L2是4路的,如果数据被加载到其中一路,则应该刚好停留在L2缓存中(其中一路覆盖64 KiB),但上面的结果表明它没有。

您可以使用我的uarch-bench程序在Linux上在自己的硬件上运行这些测试。旧系统的结果尤其有趣。

Skylake服务器(SKLX)

Skylake服务器上prefetchnta的报告行为与Skylake客户端有different L3缓存架构,存在显着差异。特别是,用户Mysticial reports that使用prefetchnta获取的行在任何缓存级别中都不可用,一旦它们从L1中驱逐出去,就必须从DRAM中重新读取。

最有可能的解释是,由于prefetchnta的存在,它们根本没有进入L3 - 这很可能是因为在Skylake服务器中,L3是专用L2缓存的非包容性共享受害者缓存,因此使用prefetchnta绕过L2缓存的行很可能永远没有机会进入L3。这使得prefetchnta功能更加纯粹:较少的缓存级别被prefetchnta请求污染,但也更加脆弱:在L1中未能读取nta行并在其被驱逐之前意味着另一个完整的往返内存:由prefetchnta触发的初始请求完全被浪费。

根据英特尔的手册,prefetchnta 仅在 L3 中每个组使用一种方式,将污染限制为 n 路组相关高速缓存的 1/n。(这适用于拥有包容性 L3 的新CPU。我很好奇 SKX 的表现,其中L3不再是包容性的。) - Peter Cordes
@PeterCordes - 是的,也许它根本不会将它加载到 L3 中。我们知道 L3 是否仍具有 L1/L2 中所有行的标记,以便其可以充当嗅探过滤器吗? 您在Intel手册的哪个位置可以看到该信息? 我查看了当前的优化手册(248966-038),它明确说明的每个地方都是“仅将数据带入二级缓存的一条路中”。我从未看到关于 L3 的任何提及。尽管文本中还提到了与 P4 和其他古老架构相关的问题。 - BeeOnRope
优化手册,2016年6月版。第7.3.2节:“基于Nehalem、Westmere、Sandy Bridge和更新微架构的Intel Xeon处理器:必须使用快速替换将其提取到第三级缓存中”,第280页。对于基于这些uarches(即“core i7”)的“Core”处理器,它是“可能”而不是“必须”,并描述了绕过L2。 - Peter Cordes
我认为SKX必须仍然具有包容标记,以跟踪缓存在内部高速缓存中的内容。我不知道这是单独的,还是作为L3中额外的方式实现的,或者可能的设计是什么样子的。实际上在各个地方发送嗅探请求是不可行的。所有我读到的都是基于专利和KNL的猜测:https://www.anandtech.com/show/11550/the-intel-skylakex-review-core-i9-7900x-i7-7820x-and-i7-7800x-tested/5。但该文章在除缓存之外的微架构细节方面并不是很好; 许多错误,如说IDQ在禁用HT时为128个uops。 - Peter Cordes
哦,太好了,我在扫描prefetchnta参考资料时错过了那个页面。整个页面非常有趣。我看到它说nt1nt2是相同的,我的测试显示L1包含大小有所不同,但也许这只是一个测试工件。关于手动版本,很奇怪:主要的英特尔下载链接似乎是旧手册,但这个新手册从其他几个地方链接(例如sandpile)。@PeterCordes - BeeOnRope
显示剩余2条评论

6
这个问题让我阅读了一些资料...查看了Intel MOVNTDQA的手册(使用Sep'14版本),有一个有趣的声明 -
处理器实现可以使用与此指令相关联的非暂态提示,如果内存源是WC(写组合)内存类型。如果内存源是WB(写回)内存类型,实现还可以使用与此指令相关联的非暂态提示。
稍后又说到 -
被读取区域的内存类型可以覆盖非暂态提示,如果非暂态读取所指定的内存地址不是WC内存区域。
因此,除非您的内存类型是WC,否则似乎没有保证非暂态提示会起作用。我不太清楚WB memtype评论的含义,也许一些Intel处理器允许您使用它来减少缓存污染的好处,或者他们想为未来保留这个选项(这样你就不会开始在WB mem上使用MOVNTDQA,并假设它总是会表现相同),但很明显WC mem是真正的用例。您希望此指令为某些本来无法缓存的内容提供一些短期缓冲。
现在,从预取描述中看到:
从不可缓存或WC内存预取将被忽略。
所以这基本上结束了故事——你的想法绝对正确,这两个指令可能不会被使用,也不太可能起作用,很可能其中一个将被忽略。
好吧,但这两个指令有机会真的能一起工作吗(如果处理器为WB内存实现NT加载)?嗯,再次阅读MOVNTDQA时,另一件事引起了注意:
缓存中的任何内存类型别名行都将被窃听和刷新。
哎呀。因此,如果您成功地将预取放入缓存中,您实际上很可能会降低任何连续流式加载的性能,因为它必须先刷新该行。这不是一个美好的想法。

谢谢@Leeor,正如我回复Peter的时候所说,我会编写三种方法并进行分析和发布结果=) - BlueStrat
@BlueStrat - 你找到了什么? - BeeOnRope

2

注意:我写这篇答案时知识水平较低,但我认为它仍然可以并且有用。

MOVNTDQA(在WC内存上)和PREFETCHNTA都不会影响或触发任何缓存硬件预取器。非临时提示的整个思想是完全避免缓存污染,或者至少尽可能地将其最小化。

只有一小部分(未记录)缓冲区称为流式加载缓冲区(这些与线填充缓冲区和L1缓存分离),用于保存使用MOVNTDQA获取的缓存行。因此,您基本上需要立即使用获取的内容。此外,MOVNTDQA在大多数英特尔处理器上仅适用于WC内存。在英特尔ADL的GLC核心上,MOVNTDQA在类型为WB的内存位置上默认使用非临时协议。但是,无论如何,NT提示都不能覆盖有效内存类型,因此仍保留了WB排序语义。这不是破坏性更改,并且与文档一致。

PREFETCHNTA指令非常适合您的场景,但您必须弄清楚如何在代码中正确使用它。来自英特尔优化手册第7.1节:

如果您的算法是单遍,则使用PREFETCHNTA。如果您的算法是多遍,则使用PREFETCHT0。

PREFETCHNTA指令提供以下好处:

  • PREFETCHNTA会将包含指定地址的特定缓存行提取到至少L3缓存,也可能是更高级别的缓存层次结构(详见Bee和Peter的回答以及第7.3.2节)。在每个缓存级别中,如果需要从集合中清除一行,则可能/应该/更有可能被视为首先要清除的行。在使用PREFETCHNTA增强的单遍算法(例如计算大型数字数组的平均值)的实现中,稍后预取的缓存行可以放置在与使用PREFETCHNTA预取的那些行相同的块中。因此,即使获取的数据总量很大,整个缓存中只会受到一种方式的影响。驻留在其他方式中的数据仍将被缓存,并且在算法终止后可用。但这是一把双刃剑。如果两个PREFETCHNTA指令彼此太接近,并且指定的地址映射到相同的缓存组,则只有一个指令会生效。
  • 使用PREFETCHNTA预取的缓存行与使用相同硬件一致性机制的任何其他缓存行一样保持一致。
  • 它适用于WB、WC和WT内存类型。您的数据很可能存储在WB内存中。
  • 正如我之前所说,它不会触发硬件预取。因此,它也可用于改善不规则内存访问模式的性能,正如英特尔推荐的那样。

执行PREFETCHNTA的线程可能无法有效地从中受益,具体取决于同一物理核心上运行的任何其他线程的行为,以及处理器的其他物理核心或共享相同一致性域的其他处理器上的核心的行为。诸如固定、优先级提升、基于CAT的缓存分区和禁用超线程等技术可能有助于使该线程有效地运行。还要注意,PREFETCHNTA被归类为一种推测性负载,因此它与三个栅栏指令并发。


1
在当前的英特尔硬件上,WB内存上的movntdqa忽略了NT提示。因此,它会触发常规预取,并运行像movdqa + ALU uop一样。否则,它将因仅执行需求缺失而具有糟糕的吞吐量,这可能是它忽略NT提示的原因。我对这个问题的答案进行了一半的更新,其中更详细地说明了这一点。无论如何,在当前硬件上,SW NT预取是最小化WB内存负载污染的唯一选择,但它很脆弱,特别是在L3不包含的SKX上;早期驱逐意味着从DRAM重新加载。 - Peter Cordes
2
你如何确定prefetchnta在填充缓存行的所有级别中都有特殊处理(仅填充单个路和/或被标记为“驱逐下一个”)?当我测试时,我发现它似乎在L3中有特殊处理(即,它只使用了L3的一部分),但在L1中没有(即,在那里它似乎表现正常,能够使用全部32 KiB而不会被首先驱逐)。这些行似乎根本没有被带入L2。 - BeeOnRope
@BeeOnRope 是的,这并不是一个保证。事实上,支持这一点会有一些小的硬件开销(您需要在每个获取的高速缓存行中使用一个NT属性位+相关逻辑来处理它),因此可能不会被实现。 - Hadi Brais
仅将数据获取到L1的一行是非常脆弱的,因为任何对相同集合的访问都会破坏它,并且由于L1的大小小而关联性高,应用程序通常无法精确控制其所有内存访问的页面偏移量,这很可能发生。此外,对于访问多个内存流的任何代码,这将使“prefetchnta”几乎无用(因为任何其他流几乎肯定会破坏L1中的NTA访问)。 - BeeOnRope
所以我认为即使忽略硬件成本,在L1中你也不会想要完全按照那样实现,否则它将非常难以有效使用。更多的是关于避免其他缓存的污染,这些缓存要大得多,因此当你完全污染它们时,总成本会更高。 - BeeOnRope

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接