非临时负载和硬件预取器,它们可以一起工作吗?
Posted
技术标签:
【中文标题】非临时负载和硬件预取器,它们可以一起工作吗?【英文标题】:Non-temporal loads and the hardware prefetcher, do they work together? 【发布时间】:2015-11-13 06:04:05 【问题描述】:当从连续的内存位置执行一系列 _mm_stream_load_si128()
调用 (MOVNTDQA
) 时,硬件预取器是否仍会启动,或者我应该使用显式软件预取(带有 NTA 提示)以获得在避免缓存污染的同时预取的好处?
我之所以这么问是因为他们的目标在我看来是矛盾的。流式加载将绕过缓存获取数据,而预取器尝试主动将数据获取到缓存中。
当顺序迭代大型数据结构时(处理过的数据不会在很长一段时间内被修改),避免污染 chache 层次结构对我来说是有意义的,但我不想招致频繁的 ~100 循环惩罚因为预取器处于空闲状态。
目标架构是英特尔 SandyBridge
【问题讨论】:
好问题。有一个prefetchnta
,但我忘记了我读到的关于这个案例的内容。
根据一些较早的英特尔文档,非临时加载与正常对齐加载相同,除非内存不可缓存。我的个人经验已经证实它们对正常数据没有性能差异。但这又回到了 Nehalem/Sandy Bridge 时代。我不知道 Haswell 或 Skylake 是否有任何变化。
@PeterCordes prefetchnta
仅拉入 L1 缓存而不是所有缓存。也就是说,我不知道它如何与硬件预取器交互。在内存访问“足够随机”以致硬件预取器失败但“足够顺序”以使用完整缓存线的情况下(就像许多缓存阻塞优化的情况一样),我发现软件预取使得在没有超线程的情况下存在巨大差异。 (~10%) 但我发现prefetcht0
和prefetchnta
之间没有明显的区别。
@Mysticial:L3 包含在最近的 Intel 设计中,因此 L3 标签可用于缓存一致性检查。如果另一个内核修改了该缓存行,则存在于 L1 但不是 L3 的缓存行可能会过时,但我认为 IA32 的缓存一致性模型不允许这样做(因此不能以这种方式实现)。 prefetchnta
是在 PIII 时代引入的,在多核 CPU 之前。如果它在当前设计上与prefetch0
完全相同,我一点也不感到惊讶,例如lddqu
现在与movdqu
完全相同。也许prefetchnta
使缓存行更有可能再次被快速驱逐。
@PeterCordes 感谢您对缓存的洞察。我从来没有从缓存一致性的角度考虑过这个。
【参考方案1】:
MOVNTDQA
(在 WC 内存上)和PREFETCHNTA
都不会影响或触发任何缓存硬件预取器。非临时提示的整个想法是完全避免缓存污染或至少尽可能减少它。
只有极少数(未记录)的缓冲区称为流加载缓冲区(它们与行填充缓冲区和 L1 缓存分开)来保存使用 MOVNTDQA
获取的缓存行。所以基本上你需要立即使用你获取的东西。另外MOVNTDQA
只对WC内存有效。
PREFETCHNTA
指令非常适合您的场景,但您必须弄清楚如何在代码中正确使用它。来自英特尔优化手册第 7.1 节:
如果您的算法是单通道的,请使用 PREFETCHNTA。如果你的算法是 多遍使用 PREFETCHT0。
PREFETCHNTA
指令提供以下好处:
PREFETCHNTA
增强的单遍算法(例如计算大量数字的平均值)的实现中,稍后预取的缓存行可以与同样使用 @ 预取的行放置在同一块中987654328@。因此,即使要获取的数据总量很大,也只有整个缓存中的一种方式会受到影响。以其他方式驻留的数据将保持缓存状态,并在算法终止后可用。但这是一把双刃剑。如果两条PREFETCHNTA
指令彼此距离太近,并且如果指定的地址映射到同一个缓存集,那么只有一条会存活。
使用 PREFETCHNTA
预取的缓存行与使用相同硬件一致性机制的任何其他缓存行一样保持一致。
它适用于 WB、WC 和 WT 内存类型。您的数据很可能存储在 WB 内存中。
就像我之前说的,它不会触发硬件预取。正是出于这个原因,它还可以用于提高英特尔推荐的不规则内存访问模式的性能。
执行PREFETCHNTA
的线程可能无法有效地从中受益,具体取决于同一物理内核、同一处理器的其他物理内核或其他处理器的内核上的任何其他运行线程的行为共享相同的相干域。固定、优先级提升、基于 CAT 的缓存分区和禁用超线程等技术可以帮助该线程高效运行。另请注意,PREFETCHNTA
被归类为推测性负载,因此它与三个栅栏指令并发。
【讨论】:
movntdqa
在 WB 内存上忽略 NT 提示,在当前 Intel 硬件上。所以它确实触发常规预取,并运行 lie movdqa
+ ALU uop。 (否则它会因为只做需求未命中而产生糟糕的吞吐量,这可能是它忽略 NT 提示的原因。我对这个问题的回答有一个半完成的更新,它更详细地说明了这一点。)无论如何,这就是为什么 SW NT预取是在当前硬件上最小化 WB 内存负载污染的唯一选择,但它很脆弱,尤其是在 L3 不包含在内的 SKX 上;提前驱逐意味着从 DRAM 重新加载。
你怎么确定 prefetchnta
在 所有 级别的缓存中具有特殊处理(仅填充单一方式和/或被标记为“下一个驱逐”)填充行在?当我测试它时,我发现它在 L3 中似乎有特殊处理(即它只使用了 L3 的一部分),但在 L1 中没有(即它似乎在那里表现正常,能够使用所有 32 KiB 和不是先被驱逐)。线条似乎根本没有被带入 L2。
@BeeOnRope 是的,这并不是真正的保证。实际上,支持它有一些小的硬件开销(每个获取的缓存行都需要一个 NT 属性位 + 相关逻辑来处理它),所以它可能无法实现。
好吧,只取到 L1 的一行也是非常脆弱的,因为对同一集合的任何访问都会破坏它,并且考虑到 L1 的小尺寸和高关联性L1,并且应用程序通常不会精确控制所有内存访问的页面偏移量,这很可能。此外,它会使prefetchnta
对任何访问多个内存流的代码毫无用处(因为任何额外的流几乎肯定会破坏 NTA 对 L1 的访问)。
所以我认为即使忽略硬件成本,您也不希望完全像在 L1 中那样实现它,否则将很难有效使用。这更多是为了避免对其他缓存的污染,这些缓存更大,因此当您完全污染它们时意味着更高的总成本。【参考方案2】:
我最近对prefetch
的各种口味进行了一些测试,而answering another question 我的发现是:
使用prefetchnta
的结果与Skylake客户端上的以下实现一致:
prefetchnta
将值加载到 L1
和 L3
而不是 L2
(实际上,如果 L2
已经存在该行,它似乎可能会被逐出)。
似乎将值“正常”加载到 L1,但在 L3 中以较弱的方式加载,因此它被更快地驱逐(例如,仅加载到集合中的单一方式,或者设置其 LRU 标志,使其将成为下一个受害者)。
prefetchnta
与所有其他预取指令一样,使用 LFB 条目,因此它们并不能真正帮助您获得额外的并行性:但 NTA 提示在此处可用于避免 L2 和 L3 污染。
当前的优化手册 (248966-038) 在一些地方声称 prefetchnta
确实将数据带入 L2,但仅以一种方式脱离集合。例如,在 7.6.2.1 视频编码器中:
为视频编码器实现的预取缓存管理 减少内存流量。二级缓存污染减少 通过防止一次性使用的视频帧数据进入 二级缓存。使用非时间 PREFETCH (PREFETCHNTA) 指令仅将数据带入二级缓存的一种方式, 从而减少对二级缓存的污染。
这与我在 Skylake 上的测试结果不一致,其中使用 prefetchnta
跨越 64 KiB 区域显示的性能几乎与从 L3 获取数据完全一致(每个负载约 4 个周期,MLP 因子为 10和大约 40 个周期的 L3 延迟):
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-way,如果数据以一种方式加载,它应该几乎不会留在 L2 缓存中(其中一种方式覆盖 64 KiB),但上面的结果表明它没有.
您可以使用我的uarch-bench 程序在您自己的 Linux 硬件上运行这些测试。旧系统的结果会特别有趣。
Skylake 服务器 (SKLX)
prefetchnta
在具有different L3 缓存架构的 Skylake 服务器上报告的行为与 Skylake 客户端有很大不同。特别是,使用 prefetchnta
获取的用户 Mysticial reports that 行在任何缓存级别均不可用,一旦从 L1 中逐出,就必须从 DRAM 重新读取。
最可能的解释是,由于prefetchnta
,他们根本没有进入 L3 - 这很可能是因为在 Skylake 服务器中,L3 是私有 L2 缓存的非包容性共享受害者缓存,所以行使用prefetchnta
绕过L2缓存很可能永远没有机会进入L3。这使得prefetchnta
在功能上更纯粹:更少的缓存级别被prefetchnta
请求污染,但也更脆弱:在被驱逐之前从L1 读取nta
行的任何失败都意味着另一个完整的内存往返: prefetchnta
触发的初始请求完全被浪费了。
【讨论】:
根据 Intel 的手册,prefetchnta
在 L3 中每组只使用一种方式,将污染限制为 n 路组关联缓存的 1/n。 (这适用于具有包容性 L3 的新 CPU。我很好奇 SKX 会做什么,而 L3 不再具有包容性。)
@PeterCordes - 是的,也许它根本不会将它加载到 L3 中。我们是否知道 L3 是否仍然具有 L1/L2 中所有行的标签,以便它可以充当窥探过滤器?您在英特尔的手册中哪里可以看到这些信息?我浏览了当前的优化手册 (248966-038),它明确指出的每个地方都是“将数据仅带入 二级缓存 的一种方式”。我从未见过任何提及 L3 行为的内容。很多文字仍然提到与 P4 和其他古代建筑有关的问题。
优化手册,2016年6月版。第 7.3.2 节:“基于 Nehalem、Westmere、Sandy Bridge 和更新微架构的英特尔至强处理器:必须通过快速替换获取到 3 级缓存”,第 280 页。对于基于这些的“核心”处理器uarches(即“core i7”),它是“may”而不是“must”,并描述了绕过L2。
我认为 SKX 必须仍然具有包容性标签来跟踪内部缓存中缓存的内容。 IDK 如果那是分开的,或者在 L3 中作为额外的方式实现,或者什么样的设计是可能的。实际上在整个地方发送窥探请求是不合理的。我所读到的只是基于专利和 KNL 的猜测:anandtech.com/show/11550/…。但是那篇文章对缓存以外的东西的微架构细节不是很好。很多错误,比如说 IDQ 是 128 uops,HT 被禁用。
我猜我的 PDF 副本已过期:我查看的是 248966-033,而不是 -38。说 L2 的地方可能应该说“最后一级”。 (除了在 Broadwell 上,eDRAM 在技术上可以作为最后一级缓存,但我认为 Broadwell 上的 LLC 即使在带有 eDRAM 的 CPU 上仍然通常指的是 L3。顺便说一句,带有 eDRAM 的 SKL 将其用作内存端缓存,而不是最后一级缓存。)【参考方案3】:
根据Patrick Fay (Intel)'s Nov 2011 post:,“在最新的 Intel 处理器上,prefetchnta 将一条线从内存带入 L1 数据缓存(而不是其他缓存级别)。”他还说,您需要确保不要太晚预取(硬件预取已经将其拉入所有级别)或太早(在您到达那里时被驱逐)。
正如 OP 上的 cmets 中所讨论的,当前的 Intel CPU 具有一个大型共享 L3,其中包括所有每核缓存。这意味着缓存一致性流量只需检查 L3 标签,以查看缓存行是否可能在每个核心 L1/L2 中的某处被修改。
IDK 如何协调 Pat Fay 的解释与我对缓存一致性/缓存层次结构的理解。我想如果它确实进入 L1,它也必须进入 L3。也许 L1 标签有某种标志表明这条线是弱序的?我最好的猜测是他在简化,并说 L1 实际上只进入填充缓冲区。
这个Intel guide about working with video RAM 谈论使用加载/存储缓冲区而不是缓存行的非临时移动。 (请注意,这可能仅适用于 uncacheable 内存。)它没有提到预取。它也很古老,早于 SandyBridge。然而,它确实有这个多汁的报价:
普通加载指令以 指令要求的大小相同。相比之下,流式加载 诸如 MOVNTDQA 之类的指令通常会拉取一个完整的缓存行 数据到 CPU 中的一个特殊的“填充缓冲区”。后续流式加载 将从该填充缓冲区中读取,从而减少延迟。
然后在另一段中,说典型的 CPU 有 8 到 10 个填充缓冲区。 SnB/Haswell still have 10 per core.。再次注意,这可能仅适用于不可缓存的内存区域。
movntdqa
on WB (write-back) memory not 弱排序(see the NT loads section of the linked answer),因此它不允许“陈旧”。与 NT 存储不同,movntdqa
和 prefetchnta
都不会改变 Write-Back 内存的内存顺序语义。
我没有测试过这个猜测,但是现代 Intel CPU 上的 prefetchnta
/ movntdqa
可以将缓存线加载到 L3 和 L1,但可以跳过 L2(因为 L2 不是包括或不包括 L1)。 NT 提示可以通过将缓存行放置在其集合的 LRU 位置来产生影响,它是要被驱逐的下一行。 (普通缓存策略在 MRU 位置插入新行,距离被驱逐最远。见this article about IvB's adaptive L3 policy for more about cache insertion policy)。
IvyBridge 上的预取吞吐量仅为每 43 个周期一次,因此如果您不希望预取降低 IvB 上的代码速度,请注意不要预取过多。来源:Agner Fog's insn tables and microarch guide。这是一个特定于 IvB 的性能错误。在其他设计中,过多的预取只会占用原本可能是有用指令的微指令吞吐量(除了预取无用地址的危害)。
关于一般的 SW 预取(不是 nt
那种):Linus Torvalds 发布了他们如何 rarely help in the Linux kernel, and often do more harm than good。显然,在链表末尾预取 NULL 指针会导致速度变慢,因为它会尝试填充 TLB。
【讨论】:
+1 很好的研究!是的,我完全禁用了任何针对 Ivy Bridge 的预取。而且我可以确认预取空值是一个糟糕的主意。这是我尝试避免使用特定功能的“无预取”版本的一种方法。完全没用。 VTune 为此对我大喊大叫。 @Leeor:IvB 每 43 个周期只能退出一条prefetch*
指令。 SnB 和 Haswell 可以每 0.5 个周期引退一个。 (它们在加载端口上运行。)因此,过度使用预取可能会导致预取指令本身成为 IvB 的瓶颈,尤其是。当数据已经在缓存中时。
我测试了一个独立预取循环(L1 常驻,以避免内存限制),吞吐量为 0.5。我想我稍后会就此提出一个问题,也许我做错了什么。
当我在 VTune 下查看它时,案例 1(仅流式负载)显示了这些负载所花费的所有时间。这并不奇怪,它们来自记忆。在案例 2 和 3(使用预取)中,VTune 显示所有时间都花在了预取本身上,而流式加载花费的时间为零。这让我感到惊讶,因为它表明进行中的预取数量有限,当达到限制时它们将阻止执行。如果它们没有阻塞,如果内存控制器无法跟上预取请求,则惩罚仍应显示在负载中。
@Mysticial:英特尔的手册暗示prefetchNTA
会绕过 L2 进入 L1D 和(进入 L3 的一种方式)。在 SKX 上,也许它也绕过了 L3,因为它不再具有包容性(并且只更新某种标签)。也许 SKX 在 L1D 中也通过仅获取任何给定集合的一种方式来限制污染? 32/8 = 4
,因此如果 NT 预取仅使用 L1D 的单一方式,那么 4kiB 几乎不足以在您获取数据之前踩到数据。 (IDK,如果这是一个可能的设计更改,但尝试更小的预取距离)。否则可能是某种设计错误......【参考方案4】:
这个问题让我做了一些阅读...查看 MOVNTDQA 的英特尔手册(使用 14 年 9 月版),有一个有趣的声明 -
处理器实现可以使用非临时提示 如果内存源是 WC(写 结合)内存类型。一个实现也可以使用 与该指令相关的非时间提示,如果内存 source 是 WB(回写)内存类型。
以后 -
正在读取的区域的内存类型可以覆盖非临时的 提示,如果为非临时读取指定的内存地址不是 WC 内存区域。
因此,除非您的内存类型是 WC,否则似乎无法保证非临时提示会执行任何操作。我真的不知道 WB memtype 注释是什么意思,也许某些英特尔处理器确实允许您使用它来减少缓存污染,或者他们可能希望将来保留此选项(所以您不要开始使用WB mem 上的 MOVNTDQA 并假设它的行为始终相同),但很明显 WC mem 是这里的真正用例。您希望这条指令为原本完全无法缓存的内容提供一些短期缓冲。
现在,另一方面,查看 prefetch* 的描述:
来自不可缓存或 WC 内存的预取将被忽略。
所以这几乎结束了故事 - 你的想法是绝对正确的,这两个可能不是故意的,也不太可能一起工作,很可能其中一个会被忽略。
好的,但是这两个是否有可能实际工作(如果处理器为 WB 内存实现 NT 加载)?好吧,再次阅读 MOVNTDQA 的内容,其他的东西引起了人们的注意:
缓存中的任何内存类型别名行都将被窥探并 脸红了。
哎哟。因此,如果您以某种方式设法预取到缓存中,您实际上可能会降低任何连续流加载的性能,因为它必须首先刷新线路。不是个好主意。
【讨论】:
感谢@Leeor,当我回复彼得时,我将对这三种方法进行编码并分析并回发结果 =) @BlueStrat - 你发现了什么?以上是关于非临时负载和硬件预取器,它们可以一起工作吗?的主要内容,如果未能解决你的问题,请参考以下文章
SpringCloud Nacos 注册中心 -- 服务实例的权重设置(根据权重负载均衡)环境隔离(命名空间namespace)Nacos(细节分析)和Eureka的对比临时/非临时实例