【问题标题】:How do Intel Xeon CPUs write to memory?Intel Xeon CPU 如何写入内存?
【发布时间】:2015-10-16 08:08:00
【问题描述】:

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

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

那么英特尔至强 CPU 是如何写回内存的呢?您会惊讶地发现,在 Google 中找到应该众所周知的问题的答案是多么困难。

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

【问题讨论】:

  • 为什么投反对票?这是一个很好的问题!
  • @inf 这个问题的一个问题是使用术语“Intel Xeon CPU”在这里并没有做出有用的区分。 Xeon 商标自 Pentium II 架构以来一直应用于 Intel x86 CPU。从技术上讲,它并没有真正表示处理器的不同类型,而是表示处理器所针对的不同类型的客户。通过将问题限制为“企业级”CPU,它的用处不如仅询问有关 Intel x86 CPU 的问题。无论哪种方式,答案都是一样的。
  • @RossRidge 好吧,那么请您澄清一下他所指的架构,不要大肆投反对票。
  • 您的主要目标似乎是在两种算法之间做出决定(基于性能)。是否有充分的理由不只对这两种算法进行基准测试?这可能需要更多的工作,但可以保证为您提供做出选择所需的准确信息。
  • @RossRidge 我的年龄还不足以记住基于 Pentium II 的 Xeon。我将把我的兴趣限制在 Sandy Bridge 和更新的 CPU 上,因为在云服务世界中,它和你发现的一样古老。我在标题中使用 Xeon 是因为更多的人知道 Xeon 是什么,而不是 Sandy Bridge 是什么。

标签: caching optimization x86 intel cpu-cache


【解决方案1】:

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

What Every Programmer Should Know About Memory,作者 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 个字节,而且它不太可能很快被再次读取,所以它可以工作。现在我必须认真考虑使用哪种算法。
  • 缓存如果很快再次写入,将是一个很大的加速,而不仅仅是读取。但值得尝试使用movntiint32_tint64_t。如果访问模式是顺序的(即使步幅适中),硬件预取会将数据从 DRAM 带入缓存,因此写入速度会很快。 (movnti 会打败这个)。我认为movnt 获胜的情况非常罕见,但这可能是其中一种情况。理想情况下,您可以在完整程序的上下文中测试这两种方式,而不是在微台上。
  • @Eloff,non-temporal 在这种情况下没有用,如果它们不能组合成完整的 64B 行,它们仍然不会写入部分字节(假设您在本地修改了行在其他一些核心的缓存中 - 谁来进行合并?)。我认为只有一些不可缓存的内存类型会允许这样做,并且代价是巨大的(可能首先从所有其他内核窥探线路以避免失去一致性),它不用于性能,仅用于设备/IO等功能目的
  • @Leeor:那么如果在填充完整的缓存行之前刷新 NT 存储缓冲区会发生什么?像第一次正常写入缓存行那样的所有权读取会生成,然后合并并写入 DRAM?如果 NT 写入仅在写入整个缓存行时对性能有用,我应该更新我的答案。
  • 我假设首先需要读取所有权,以避免 x86 如何保持一致性的问题(因为非时间可能适用于 WB mem 类型,这不会较弱类型的任何折扣)。非临时性仍然具有性能优势,但对于部分行没有优势 - 请参阅stackoverflow.com/questions/30313600/…
【解决方案2】:

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

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

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

【讨论】:

  • 我认为 Xeon 在内存总线上一次写入 64 位?因此可以想象每个高速缓存行有 8 个脏位来跟踪哪个 8 字节字是脏的。我只是不认为它真的完成了。顺便说一句,您刚刚通过缓存失效示例描述了为什么错误共享是一个问题。内核最终可能会在缓存行之间来回进行乒乓操作,即使它们实际上并不需要相互冲突。
  • @Eloff:可以想象一个缓存行有 8 个脏位,但我认为这没有意义。拥有高速缓存行的全部原因是具有 CPU 运行的空间局部性的基本单元。如果缓存行可以被 CPU 进一步划分为单独的块,那么它就不算缓存行了,是吗?
  • 同意,这对我来说也没有意义
  • x86 模拟器PTLsim 使用有效掩码实现高速缓存行。该手册说这允许“存储时不停止”策略,即存储到非驻留缓存行可以提前提交并获取缓存行的其余部分,而不会停止处理器。它似乎足够有用,虽然我不知道是否有任何商业处理器这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-29
  • 2019-01-26
  • 2017-04-15
  • 1970-01-01
  • 2016-06-22
  • 2016-04-24
  • 1970-01-01
相关资源
最近更新 更多