在 x64 上再次读取之前在未缓存的地址写入完整的缓存行
Posted
技术标签:
【中文标题】在 x64 上再次读取之前在未缓存的地址写入完整的缓存行【英文标题】:Writing a full cache line at an uncached address before reading it again on x64 【发布时间】:2017-10-16 07:25:51 【问题描述】:在 x64 上,如果您在短时间内首先将 full 缓存行的内容写入先前未缓存的地址,然后在再次从该地址读取后不久,CPU 就可以避免必须从内存中读取该地址的旧内容?
实际上,内存的内容以前是什么并不重要,因为整个高速缓存行的数据都被完全覆盖了?我可以理解,如果它是对未缓存地址的部分缓存行写入,然后是读取,那么它将产生必须与主内存同步等的开销。
查看有关写入分配、写入组合和窥探的文档让我对这件事有些困惑。目前我认为 x64 CPU 无法做到这一点?
【问题讨论】:
据我了解,即使使用 AVX512,也无法在单个内存访问中执行 64 字节(典型缓存行大小)的传输。因此,尽管可能,但我相信没有处理器会在写入之前跳过行填充操作。如果缓存类型需要它。此外,MESI 协议在执行某些写入时需要请求所有权(显示为读取)操作 我不确定是否会有特定的优化与写入组合给定的连续写入相关的多个指令填充一行(正如你所说,你不能在一个操作中填充整个缓存行)。我想即使 MESI 目前没有,多核之间的协议也可以解决这个问题。我读得越多,我就越确定这个问题的答案是否定的。 FWIW,写入组合不使用缓存。我也会说“不”作为答案。不过请等待专家;) 哦,我的意思是一个“相似”的功能来写组合,但不一定写组合本身:-) 但是我想这样的功能需要与存储缓冲区交互(我不是硬件人员所以我真的不知道)。如果答案不是“否”,那么软件优化会很有趣...... @MargaretBloom - 我很好奇你为什么提到 AVX512 不提供这种能力? ISTM 认为对齐的 64 字节mov
将完全覆盖缓存行(但实现 CPU 是否对其进行优化以避免 RFO 是另一回事)。也许问题是当前的硬件仍然将其分成两个 32 字节的访问?
【参考方案1】:
一般来说,后续读取应该很快 - 只要 存储到加载转发 能够工作。事实上,它与写入整个缓存行完全无关:即使是较小的写入,它也应该工作(同样需要注意的是)!
通常情况下(即 WB 内存区域)映射内存上发生的情况是,存储将向 CPU 的 存储缓冲区 添加多个条目。由于关联的内存当前没有被缓存,这些条目将持续一段时间,因为将发生RFO 请求将该行拉入缓存以便可以写入。
与此同时,您发出一些针对刚刚写入的相同内存的加载,这些通常会通过 store-to-load forwarding 来满足,这几乎只是注意到存储已经在相同地址的存储缓冲区中,并将其用作加载的结果,而无需进入内存。
现在,存储转发并不总是有效。特别是,当负载仅部分与最近涉及的存储重叠时,它从不在任何 Intel(或可能是 AMD)CPU 上工作。也就是说,如果您向地址 10 写入 4 个字节,然后从地址 9 读取 4 个字节,则只有 3 个字节来自该写入,而 9 处的字节必须来自其他地方。在这种情况下,所有 Intel CPU 只需等待所有相关存储都被写入,然后解决负载。
过去,还有许多其他情况也会失败,例如,如果您发出一个完全包含在早期存储中的较小读取,它通常会失败。例如,给定地址 10 的 4 字节写入,从地址 12 读取的 2 字节完全包含在较早的写入中 - 但通常不会转发,因为硬件不够复杂,无法检测到这种情况。
然而,最近的趋势是,除了上面提到的“未完全包含读取”案例之外的所有案例都在现代 CPU 上成功转发。血淋淋的细节覆盖得很好,有漂亮的图片,on stuffedcow 和 Agner 在他的microarchitecture guide 中也很好地覆盖了它。
从上面的链接文档中,以下是 Agner 关于 Skylake 上的存储转发的说法:
Skylake 处理器可以将内存写入转发到后续读取 在特定条件下来自同一地址。商店转发是 比以前的处理器快一个时钟周期。内存写入 随后从同一地址读取需要 4 个时钟周期 32 位或 64 位操作数的最佳情况,其他 5 个时钟周期 操作数大小。
存储转发有最多 3 个时钟周期的额外惩罚,当 128 或 256 位的操作数未对齐。
存储转发通常需要额外的 4 - 5 个时钟周期,当 任何大小的操作数跨越缓存线边界,即地址 可被 64 个字节整除。
从同一地址进行一次较小的读取之后的写入很少或 没有惩罚。
写入 64 位或更少,然后进行较小的读取,惩罚为 1 - 3 个时钟,当读取偏移但完全包含在 写入覆盖的地址范围。
对齐的 128 或 256 位写入,然后读取一个或两个 两半或四等分中的很少或没有 惩罚。不适合一半或四分之一的部分阅读 可能需要额外的 11 个时钟周期。
读取大于写入,或读取涵盖两者 写入和未写入字节,大约需要 11 个时钟周期 额外的。
最后一种情况,即读大于写,肯定是存储转发停止的情况。 11 个周期的引用可能适用于所有涉及的字节都在 L1 中的情况 - 但是某些字节根本没有被缓存的情况(你的场景)它当然可以采取 DRAM 未命中的顺序,这可以是数百个周期。
最后,请注意,以上所有内容都与写入整个缓存行无关 - 如果您写入 1 个字节然后读取同一字节,则缓存行中的其他 63 个字节保持不变,它也可以正常工作。
的效果类似于您提到的完整缓存行,但它处理 写入组合 写入,这些写入可通过将内存标记为写入组合来获得(而不是通常的回写)或使用non-temporal 存储指令。 NT 指令主要针对写入 随后不会很快被读取的内存,跳过 RFO 开销,并且可能不会转发到后续加载。
【讨论】:
很棒的答案。你知道这是否是从 Sandy Bridge 开始支持(读取匹配的写入)的东西吗? 我不确定您所说的“这个”是什么意思 - 但如果您的意思是存储转发,它的支持时间比这要长得多。例如我 linked above 的 Agner 指南,它已经谈到了 Pentium Pro 中的存储转发,因此至少可以追溯到几十年前。 @iam 我不清楚这在多核情况下是如何工作的。假设核心 A 覆盖了之前未缓存的整个缓存行,而不读取任何字节,然后在核心 B 尝试读取该缓存行之后的某个时间。核心 A 是否会将缓存行内容发送到缓存供 B 读取(我假设核心 B 不能直接从核心 A 的存储缓冲区读取?)而不必加载缓存行本身并经历缓存未命中?另外,如果 B 在 A 仍在写入时尝试读取缓存行怎么办 - 那么 A 会遇到未命中吗?以上是关于在 x64 上再次读取之前在未缓存的地址写入完整的缓存行的主要内容,如果未能解决你的问题,请参考以下文章