Intel Xeon CPU如何写入内存?

27

我正在尝试在两种算法之间做出决定。一种将8个字节(两个对齐的4字节单词)写入2个高速缓存行,另一种则会写入3个完整的高速缓存行。

如果CPU仅将更改的8个字节写回内存,则第一种算法使用的内存带宽要少得多:8个字节与192个字节相比。如果CPU写入整个高速缓存行,则128字节和192字节之间的差异不那么显著。

那么,英特尔Xeon CPU如何将数据写回内存呢?你会惊讶地发现在Google上找到一个本应该广为人知的答案是多么困难。

据我理解,写操作进入了存储缓冲区,然后再写入高速缓存。当脏的高速缓存行被逐出缓存时,它们可能才会被写入内存,但英特尔是否跟踪缓存行中哪些部分是脏的,或者只是将整个缓存行倾倒出去呢?我认为他们不会追踪缓存行粒度以下的事情,并且在高速缓存线路被逐出之前,任何东西都不会被写回内存。


8
为什么会有负评?这是一个很棒的问题! - Stephan Dollberg
5
问题中的一个问题是使用术语“Intel Xeon CPU”在这里并没有起到有用的区分作用。自Pentium II架构以来,Xeon商标已被应用于Intel x86 CPU。从技术上讲,它并不真正表示一种不同类型的处理器,而更多地表示处理器面向的不同类型的客户。通过将问题仅限于“企业级”CPU,它比仅询问有关Intel x86 CPU的问题要不太有用。答案无论如何都将相同。 - Ross Ridge
3
@RossRidge 好的,那就直接询问他指的是哪种架构,不要一味地进行贬低投票。 - Stephan Dollberg
@inf 给一个问题点踩并不是“狂欢”。这也不是这里的工作方式。人们可以自由地根据实际写下的问题进行投票。他们不必猜测或询问发布者的真正意图。无论如何,我实际上并不知道为什么有人会以这种方式投票。我只是指出了帖子中可能存在的问题,这可能是原因之一。 - Ross Ridge
5
你的主要目标似乎是决定两种算法(基于性能)之间的选择。有没有很好的理由不直接对两种算法进行基准测试?这可能需要更多的工作,但它保证会给你所需的信息来做出选择。 - Hugh Allen
2
@RossRidge 我还不够老,无法记得基于 Pentium II 的 Xeon 处理器。我将把兴趣限制在 Sandy Bridge 及更新的 CPU 上,因为在云服务世界中,这已经是最老的了。我在标题中使用 Xeon 是因为更多的人知道 Xeon 是什么,而不是 Sandy Bridge。 - Eloff
2个回答

18

即使不考虑缓存,内存本身的局部性也很重要。对于一个脏的缓存行来说,64B连续字节的突发写入比16个4B到16个不同地址的写操作快得多。或者换句话说,在缓存行中写回整个缓存行并不比仅写回几个更改过的字节慢多少。

《每个程序员都应该了解的关于内存的知识》(作者:Ulrich Drepper)解释如何避免编程时的内存瓶颈问题。他详细介绍了DRAM寻址的一些细节。DRAM控制器必须选择一行,然后选择一列。访问另一个虚拟内存页面也可能导致TLB未命中。

DRAM具有用于传输连续数据块的突发传输命令。(显然是为了CPU写回缓存行而设计的)。现代计算机的内存系统是为写入整个缓存行的使用模式进行优化的,因为这几乎总是会发生的情况。

缓存行是CPU跟踪脏或干净的单位。可以使用小于当前缓存行大小的线大小来跟踪数据的脏状态,但这需要额外的晶体管,不值得。多级缓存被设置为传输整个缓存行,以便在需要读取整个缓存行时速度尽可能快。

有所谓的非暂态读/写( movnti/movntdqa )可以绕过缓存。这些用于处理在缓存中再次不会被访问的数据(因此是非暂态)。对于可能受益于缓存的数据来说,它们是一个坏主意,但它们可以让你将4个字节写入内存,而不是整个缓存行。根据该内存范围的MTRR,写操作可能会或可能不会受到写合并的影响。(这与内存映射的I/O区域有关,其中两个相邻的4B写操作与一个8B写操作不同。)

这个算法仅涉及两个缓存行,因此在这方面具有优势,除非需要更多计算量或特别的分支来确定要写入哪个内存。如果你想要帮助决定,可能应该问一个不同的问题。(请参见https://stackoverflow.com/tags/x86/info 中Agner Fog的指南等信息,以便自己做出决策.)

请查看Cornstalks关于不同CPU上的多个线程访问同一内存的风险警告。这可能导致比单线程程序的额外写入慢得多的情况。


我忘记了非时态写入。使用它们,我可以让一个算法只写入8个字节,而且不太可能很快再次读取,所以这可能有效。现在我必须认真考虑使用哪个算法。 - Eloff
如果缓存不仅仅是读取,而是经常被写入,那么它将大大提高速度。但是,使用movntiint32_tint64_t也值得一试。如果访问模式是顺序的(即使有适度大小的步幅),硬件预取将从DRAM中将数据带入缓存,因此写入将很快。(movnti将破坏这个过程)。我认为movnt很少能够胜出,但这可能是其中之一。理想情况下,您可以在完整程序的上下文中测试两种方法,而不是微基准测试。 - Peter Cordes
1
@Eloff,在这种情况下,非时态的不是很有用,如果它们不能组合成完整的64B行,则仍然不会写入部分字节(想象一下您在其他核心缓存中本地修改了该行 - 谁将执行合并?)。我认为只有一些不可缓存的内存类型才允许这样做,而且惩罚是巨大的(可能首先从所有其他核心中窥视该行以避免失去一致性),它不是用于性能,而是用于设备/IO等功能。 - Leeor
@Leeor:如果在填充完整个缓存行之前刷新NT存储缓冲区会发生什么?像对缓存行的第一个普通写入那样进行所有权读取,然后合并并写入DRAM?如果NT写入只在写入整个缓存行时有用,则应更新我的答案。 - Peter Cordes
我认为首先需要进行读取所有权,以避免x86在维护一致性方面出现问题(因为非临时物品可能适用于WB内存类型,而不像较弱的类型那样获得任何折扣)。非临时物品仍然具有性能优势,但对于部分行来说并非如此-请参见https://dev59.com/o-o6XIcBkEYKwwoYFQLr#30347796?s=1|1.1388#30347796。 - Leeor

9
为了让CPU仅将脏字节写回内存,它需要为缓存中的每个字节存储一个脏位。这是不可行的,现代CPU也没有这样做(据我所知)。CPU只有一个脏位用于缓存行。写入缓存行中的任何字节都会导致整个行被标记为脏。
当需要刷新脏缓存行时,整个行都需要被写入,因为CPU不知道哪个字节发生了更改。
这可以在缓存失效策略中看到,在一个核心中写入一个缓存行可以使不同核心中的缓存行失效(因为两个缓存行映射到同一地址),即使第一个核心使用缓存行的低半部分,第二个核心使用缓存行的高半部分。也就是说,如果核心1写入字节N,核心2正在使用字节N + 1,则即使您和我知道这不是必要的,核心2仍然必须刷新其缓存行。

我认为Xeon在内存总线上一次写入64位?因此,可以想象每个缓存行有8个脏位来跟踪哪个8字节单词是脏的。但我不认为实际上会这样做。顺便说一句,您刚刚用您的高速缓存失效示例描述了为什么会出现虚假共享问题。即使核心之间不需要相互冲突,它们也可能反复地来回传递缓存行。 - Eloff
@Eloff:可以想象一下,一个缓存行有8个脏位,但我认为这没有意义。拥有缓存行的整个原因是为了让CPU在其上操作时具有空间局部性的基本单元。如果CPU可以将缓存行进一步分成单独的块,则它就不是一个很好的缓存行了,对吧? - Cornstalks
同意,这对我也没有意义。 - Eloff
x86模拟器PTLsim实现了一个带有效掩码的高速缓存行。手册称这允许“无停顿存储”策略,即对于非驻留高速缓存行的存储可以提前提交并获取其余高速缓存行而不会使处理器停顿。虽然我不知道是否有任何商用处理器这样做,但它似乎很有用。 - hayesti

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