Intel Xeon CPU 如何写入内存?

How do Intel Xeon CPUs write to memory?

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

如果 CPU 只将更改后的 8 个字节写回内存,那么第一个算法使用的内存带宽要少得多:8 字节对 192 字节。如果 CPU 写入整个缓存行,那么 128 字节和 192 字节之间的差异就不那么明显了。

那么 Intel Xeon CPU 如何写回内存?您会惊讶于在 Google 中找到本应众所周知的答案是多么困难。

据我了解,写入进入存储缓冲区,然后进入缓存。它们可能只会在脏缓存行被从缓存中逐出时写入内存,但英特尔是否跟踪缓存行的哪些部分是脏的,或者只是转储整个缓存行?我相当怀疑他们跟踪缓存行粒度以下的东西。如果在缓存行被驱逐之前有任何东西进入内存,我也会感到非常惊讶。

为了让 CPU 只将脏字节写回内存,它需要为缓存中的每个字节存储一个脏位。这是不可行的,并且在现代 CPUs 上没有完成(据我所知)。 CPUs 对于缓存行只有一个脏位。写入缓存行中的任何字节都会导致整行被标记为脏。

当需要刷新脏缓存行时,需要写入整行,因为 CPU 不知道更改了哪个字节。

这可以在缓存失效策略中看到,其中写入一个核心中的一个缓存行可以使另一个核心中的缓存行无效(因为两个缓存行映射到相同的地址),即使第一个核心正在使用高速缓存行的低半部分,而第二个核心正在使用高速缓存行的高半部分。也就是说,如果核心 1 写入字节 N,而核心 2 使用字节 N+1,那么核心 2 仍然需要刷新其缓存行,即使你我都知道这没有必要。

即使不考虑缓存,局部性甚至对 DRAM 本身也很重要。为脏缓存行突发写入 64B 连续字节比向 16 个不同地址写入 4B 的 16 次要快得多。或者换句话说,写回整个缓存行并不比写回缓存行中的几个更改字节慢多少。

What Every Programmer Should Know About Memory,作者 Ulrich Drepper,解释了很多关于在编程时避免内存瓶颈的内容。他包含了 DRAM 寻址的一些细节。 DRAM 控制器必须 select 一行,然后 select 一列。访问另一个虚拟内存页也会导致 TLB 未命中。

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

缓存行 CPU 跟踪是否脏的单位。可以使用比存在或不存在的缓存行更小的行大小来跟踪脏度,但这将需要额外的晶体管并且不值得。设置多级缓存来传输整个缓存行,因此当需要读取整个缓存行时,它们可以尽可能快。

有所谓的绕过缓存的非临时reads/writes (movnti/movntdqa)。这些用于在无论如何都将被从缓存中逐出之前不会再次被触及的数据(因此是非时间的)。对于可以从缓存中受益的数据,它们不是一个好主意,但会让您将 4 个字节写入内存,而不是整个缓存行。根据该内存范围的 MTRR,写入可能会或可能不会进行写入合并。 (这与内存映射 i/o 区域相关,其中两个相邻的 4B 写入与一个 8B 写入不同。)

只涉及两个高速缓存行的算法在该分数上肯定具有优势,除非它需要更多的计算,或者特别是分支来确定要写入哪个内存。如果您需要帮助做出决定,也许可以问一个不同的问题。 (请参阅 https://whosebug.com/tags/x86/info 上的链接,尤其是 Agner Fog 的指南,了解有助于您自己做出决定的信息。)

有关在不同 CPU 上使用多个线程接触同一内存的危险的警告,请参阅 Cornstalks 的回答。这比单线程程序的额外写入会导致更大的减速。