“非时序”内存访问在x86中的含义是什么?

158
这是一个有点低层次的问题。在x86汇编中,有两个SSE指令:

MOVDQA xmmi,m128

MOVNTDQA xmmi,m128

IA-32软件开发人员手册表示MOVNTDQA中的“NT”代表“非临时的”,其他方面与MOVDQA相同。
我的问题是,“非临时的”的意思是什么?

8
请注意,SSE4.1中的MOVNTDQA xmmi, m128是一种非临时性加载指令(NT load),而所有其他NT指令都是存储指令,除了prefetchnta。这个被接受的答案似乎只谈到了存储指令。这是我在NT加载方面找到的内容。简而言之,CPU会希望使用NT提示来最小化缓存污染,但它们不会覆盖“正常”写回内存的强序语义,所以它们还是需要使用缓存。 - Peter Cordes
7
更新:在大多数 CPU 上(例如 Intel SnB 家族),NT loads 可能除了在 UCSW 内存区域上不会执行任何有用的操作。然而,NT/streaming stores 在普通内存上肯定有效。 - Peter Cordes
4
你的意思是USWC内存,对吗?我以前从没听说过UCSW或USWC内存。谷歌搜索错误的缩写没有帮助 :-) - Andrew Bainbridge
5
是的,WC内存类型属性。不可缓存的推测写合并。我想我当时将“不可缓存”首字母大写,并记住它应该是4个字母长的。 :P - Peter Cordes
3个回答

187

非暂态SSE指令(MOVNTI,MOVNTQ等)不遵循常规缓存一致性规则。因此,为了使它们的结果及时地被其他处理器看到,非暂态存储必须跟随一个SFENCE指令。

当数据产生且(立即)不再被消费时,存储操作会先读取整个缓存行,然后修改缓存数据的事实对性能是有害的。这个操作将数据从缓存中推出,而这些数据可能在以后还需要使用,而放弃这些缓存数据而使用不久的数据。对于像矩阵这样的大型数据结构,填充后稍后再使用。在矩阵的最后一个元素填充之前,其巨大的大小会将第一个元素逐出缓存,使写入的缓存失效。

针对此类情况,处理器提供非暂态写入操作的支持。在这个上下文中,非暂态意味着数据不会很快被重复使用,因此没有理由将其缓存。这些非暂态写操作不读取缓存行并修改它,而是直接将新内容写入内存。

来源:http://lwn.net/Articles/255364/


18
很好的回答,我只想指出在具有NT指令的处理器上,即使使用非非临时指令(即普通指令),线路高速缓存也不会被“读取然后修改”。对于一个写入到未被缓存的线路的普通指令,一个线路会被保留在缓存中,并且一个掩码指示线路哪些部分是最新的。这个网页将其称为“存储无停顿”:http://www.ptlsim.org/Documentation/html/node30.html。我找不到更精确的参考资料,我只听说过这个来自那些实现处理器模拟器的人们口中。 - Pascal Cuoq
2
实际上,http://www.ptlsim.org/ 是一个关于周期精确的处理器模拟器的网站,正是那些告诉我“存储器无停顿”的人正在做的同样的事情。如果他们看到这个评论,我最好也提一下他们:http://unisim.org/。 - Pascal Cuoq
1
从这里的答案和评论 https://stackoverflow.com/questions/44864033/make-previous-memory-stores-visible-to-subsequent-memory-loads 看来 SFENCE 可能不是必需的。至少在同一个线程中。你也可以看一下吗? - Serge Rogatch
2
@SergeRogatch 这取决于你所讨论的场景,但是在某些情况下需要使用 sfence 来进行 NT 存储,而对于普通存储来说则从不需要。如果没有 sfence,NT 存储与其他存储(无论是否为 NT 存储)之间的顺序关系将不会被其他线程看到。然而,对于执行存储操作的同一线程的读取操作,您永远不需要 sfence:给定线程始终会按照程序顺序查看其自己的存储,无论它们是 NT 存储还是其他类型的存储。 - BeeOnRope
因此,非时间相关存储必须在其后跟随一个SFENCE指令,以便其他处理器及时看到它们的结果。我不知道为什么“非时间相关存储必须在其后跟随一个SFENCE”。那么,“非时间相关存储”是否不允许内存重排序? - SungJinKang
@SungJinKang 因为非时间存储是弱排序的,所以如果多核访问目标,则需要栅栏。 - grayxu

46

Espro的观点基本上非常准确。只想补充一下:

“非时间性”指缺乏时间局部性。缓存利用两种局部性 - 空间和时间,通过使用非时间性指令,您向处理器发出信号,表明您不希望在不久的将来使用数据项。

我对使用缓存控制指令的手动编码汇编有些怀疑。根据我的经验,这些东西会导致更多的错误,而不是有效的性能提升。


关于“手写汇编使用缓存控制指令”的问题。我知道你明确说了“手写”,但像JavaVM这样的东西怎么样?这是一个更好的用例吗?JavaVM /编译器已经分析了程序的静态和动态行为,并使用这些非暂态指令。 - Pat
9
利用问题领域、算法或应用程序已知的局部性质(或缺乏这些性质)是可以的,不应该被回避。避免高速缓存污染确实是一个非常有吸引力和有效的优化任务。此外,为什么对汇编语言有抵触情绪?有大量的机会可以获得收益,而编译器无法利用它们。 - awdz9nld
7
有见识的低级程序员在小内核方面可以胜过编译器这一说法确实是正确的。这对于发表论文和博客文章非常有用,我都做过。它们也是很好的教学工具,有助于理解到底发生了什么。但是根据我的经验,在实践中,当你有一个有许多程序员参与、正确性和可维护性很重要的真实系统时,低级编码的好处几乎总是被风险所掩盖。 - Pramod
6
@Pramod,相同的论点很容易普遍适用于优化问题,并且不在讨论范围内——显然,这种权衡已经被考虑过或被认为与事实无关,因为我们已经在谈论非时间指令。 - awdz9nld

11
根据《Intel® 64和IA-32架构软件开发人员手册,卷1:基本架构》,章节“使用Intel Streaming SIMD Extensions(Intel SSE)进行编程”:
缓存暂态与非暂态数据
程序引用的数据可以是暂态的(数据将再次使用)或非暂态的(数据将被引用一次且不会在不久的将来重复使用)。例如,程序代码通常是暂态的,而多媒体数据(如3D图形应用程序中的显示列表)通常是非暂态的。为了有效利用处理器的缓存,通常希望缓存暂态数据并不缓存非暂态数据。过度使用处理器缓存的非暂态数据有时被称为“污染缓存”。SSE和SSE2可缓存控制指令使程序能够以最小化污染缓存的方式将非暂态数据写入内存。
非暂态加载和存储指令的描述。 来源:Intel 64和IA-32架构软件开发人员手册,卷2:指令集参考
LOAD(MOVNTDQA - 加载双倍四字节非暂态对齐提示)
将双倍四字节从源操作数(第二个操作数)加载到目标操作数(第一个操作数),如果内存源是WC(写组合)内存类型,则使用非暂态提示。

[...] 处理器不会将数据读入高速缓存层次结构,也不会从内存中获取相应的缓存行到高速缓存层次结构中。

需要注意的是,正如Peter Cordes所评论的,对于当前处理器上的普通WB(写回)内存而言,它并不有用,因为NT提示被忽略了(可能是因为没有NT-aware HW prefetchers),完全遵循强排序加载语义。 prefetchnta 可以用作来自WB内存的污染减少负载操作。

存储(MOVNTDQ-使用非临时提示存储打包整数)

使用非临时提示,将源操作数(第二个操作数)中的打包整数移动到目标操作数(第一个操作数)中,以防止在写入内存时缓存数据。

[...] 处理器不会将数据写入高速缓存层次结构,也不会从内存中获取相应的缓存行到高速缓存层次结构中。

使用Cache Write Policies and Performance中定义的术语,它们可以被视为写入绕过(不写分配,不在写缺失时获取)。

最后,有必要回顾John McAlpin有关非临时存储的注意事项


6
SSE4.1中的MOVNTDQA指令只对WC(不可缓存的写组合)内存区域有特殊作用,例如视频内存。它在当前硬件上对普通的WB(写回)内存没有任何用处,NT提示被忽略,并且完全遵循强顺序加载语义。prefetchnta指令可能是有用的,因为它可以从WB内存中减少污染。当前的x86架构支持从“普通”内存进行非暂态加载吗? - Peter Cordes
3
没错,NT 存储在 WB 记忆体上运作良好,并且是弱序的,通常是写入大量内存区域的良好选择。但是 NT 载入不行。x86 手册上允许 NT 提示对从 WB 记忆体载入做出某些处理,但在目前的 CPU 中它“没有任何作用”(可能是因为没有 NT 感知的硬件预取器)。 - Peter Cordes
2
@LewisKelsey:NT 存储确实覆盖了内存类型。这就是为什么它们可以在WB内存上弱有序的原因。主要效果是避免RFOs(显然,当它们到达内存时,它们发送一个使其他脏行清除的失效)。它们也可以变得无序可见,因此它们不必等待早期缓存未命中(常规)存储提交,或者等待早期缓存未命中加载获取数据。即在多处理器系统中,每个核外部的内存始终概念上是平坦/统一/同步的吗?中所问的瓶颈类型。 - Peter Cordes
@PeterCordes,您是否知道非暂态提示是否也会使UC和WP写入在存储缓冲区中弱有序?(尽管UC和WP从缓存角度覆盖了提示) - Lewis Kelsey
1
@LewisKelsey:内存排序机器清除可以杀死任何不应该提前完成的UC存储后的任何负载,如果必要的话。 除此之外,提交顺序直到存储从乱序后端退役后才会发挥作用。 这只能在存储地址uop执行之后发生,此时可以检查地址的内存类型。 存储地址uop在执行时检查TLB;这就是CPU在它们退役之前如何检测到故障存储的方式。 它不能等待SB条目准备好提交到L1d;此时执行已经超过了它。 - Peter Cordes
显示剩余5条评论

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