减少总线流量以进行缓存行失效处理

7

共享内存多处理系统通常需要为缓存一致性生成大量的流量。 核心A写入缓存。稍后,核心B可能会读取相同的内存位置。因此,即使核心A本来可以避免将数据写入主内存,但需要向核心B发送通知,告诉B如果它在缓存中持有该地址,则使其失效。

确切地说,何时需要执行此操作是个复杂的问题。不同的CPU架构具有不同的内存模型,在这种情况下,内存模型是一组保证关于事物发生顺序的内容。 内存模型越弱,A就越能放松精确发送通知给B的时间,A和B在并行处理更多任务时也更容易。有关不同CPU架构的内存模型的概述信息,请参见: https://en.wikipedia.org/wiki/Memory_ordering#Runtime_memory_ordering

所有讨论似乎都关注了何时以及什么样的顺序进行无效化。

但在我看来,在许多工作负载中,A写入的大部分数据实际上永远不会被B使用,因此,如果那些缓存失效的总线流量可以完全消除,那将是更好的选择。专门用于执行缓存一致性的硬件仍然需要存在,因为A和B有时需要共享数据,但写入共享总线是CPU可以执行的最耗能的操作之一,而电池寿命和散热通常是限制资源,因此减少总线流量将是一个有用的优化。 有没有办法做到这一点?

从效率的角度来看,理想情况是忽略总线流量是默认设置(因为大多数写入数据不与其他线程共享),并且您必须在需要缓存一致性的地方显式发出内存屏障。另一方面,由于现有代码假定其运行在x86或ARM上,这可能是不可能的;有没有办法反过来做,表示给CPU指定的缓存行永远不会被任何其他线程所关注?

我对任何系统的答案都感兴趣,但最特别是针对当前/未来服务器配置中最常见的Linux在x64,ARM或RISC-V上。

1个回答

5
真实的CPU不使用共享总线;流量通过L3缓存,其标签作为嗅探过滤器(特别是在单插槽英特尔芯片中)。或者其他微架构上类似的节省流量的方式。你说得对,向每个核心广播消息实际上会因为功耗和性能问题而变得极其昂贵,尤其是当你扩展到许多核心时。共享总线只是MESI等协议的一个简单的心理模型,而不是现代CPU的真实实现。例如,请参见 现代x86 CPU使用什么高速缓存一致性解决方案?
写回缓存需要进行写分配,因此在存储之前需要读取缓存行,以便对于该行的其他部分具有原始数据。当写触发读取时,这个读取被称为“所有权读取”(RFO),以将该行转换为MESI独占状态(可以将其转换为脏修改而无需外部流量)。RFO包括失效。
如果初始访问是只读的,那么该行通常会像RFO一样以独占状态到达,如果没有其他核心缓存了副本(即在L3(最后一级)缓存中未命中)。这意味着对于读取某些私有数据然后修改它的常见模式,流量保持在最低限度。
我认为多插槽系统将不得不窥视其他插槽或查询窥视过滤器来确定此信息,但最注重功率/能量的系统是移动设备(始终是单插槽)。

有趣的事实:Skylake-X之前(例如E5 ...-v4),英特尔2插槽Xeon芯片在套接字之间的流量没有嗅探过滤器,只是通过QPI链接向另一个套接字发送嗅探。 E7 CPU(可用于四个或更大系统)具有专用的嗅探过滤器缓存,以跟踪热线的状态,以及足够的QPI链接来交叉连接更多套接字。来源: John McCalpin在Intel论坛上的帖子,尽管我还没有找到其他数据。也许John想到了早期的系统,如Core2 / Nehalem Xeons,其中英特尔确实谈到了具有嗅探过滤器的问题,例如https://www.intel.ca/content/dam/doc/white-paper/quick-path-interconnect-introduction-paper.pdf将QPI与早期设置进行比较。并提供了一些有关可以在延迟和吞吐量之间权衡的嗅探模式的更多细节。也许英特尔只是没有以相同的方式使用“嗅探过滤器”这个术语。

有没有办法反过来做,告诉CPU某个缓存行对于任何其他线程都不感兴趣?

如果您拥有将存储数据与使缓存无效结合的高速缓存写协议,则可以跳过RFOs。例如,x86具有绕过缓存的NT存储器,并且显然快速字符串存储器(rep stos / rep movs)甚至在ERMSB之前也可以使用无RFO写协议(根据设计它的Andy Glew所说,在P6中至少如此),即使它们将其数据留在缓存层次结构中。但是,这仍然需要使其他缓存失效,除非该核心已经拥有E或M状态中的行。增强的memcpy REP MOVSB

一些CPU确实有一些仅属于每个核心的私有scratchpad memory。它不被共享,因此不需要或不可能进行显式刷新。请参阅Dr. Bandwidth在Can you directly access the cache using assembly?上的答案-这在DSP上很常见。
除此之外,通常情况下,CPU 不提供一种将内存地址空间的部分视为非协同的方法。协同性是 CPU 不想让软件禁用的保证。(可能是因为它可能会创建安全问题,例如,如果某些旧写入在操作系统对其进行校验和之后,在 DMA 到磁盘之前成为文件数据页中的可见内容,则非特权用户空间可以导致像 BTRFS 或 ZFS 这样的校验和文件系统查看文件中的坏块。)
通常,内存屏障的工作原理是使当前核心等待,直到存储缓冲区已经排空到 L1d 缓存(即先前的存储已经变成全局可见),因此,如果允许非协同 L1d,则需要其他机制来刷新它。(例如,x86 的 clflush 或 clwb 强制写回到外部缓存。)
创建大多数软件利用此功能的方式将很困难;例如,假定您可以获取本地变量的地址并将其传递给其他线程。即使在单线程程序中,任何指针也可能来自。因此,您不能默认将堆栈空间映射为非一致性或类似的东西,并编译程序以使用额外的刷新指令,以防它们获得指向非一致性内存的指针,这些指针确实需要在所有情况下都可见,否则会完全破坏整个事物的目的。

因此,这不值得追求的部分原因是,所有层级上的每个东西都必须关心此功能以使其高效。嗅探过滤器和基于目录的一致性是解决问题的足够方法,并且总体而言比期望每个人为此低级特性优化其代码要好得多!


1
而且很明显,即使在 ERMSB 之前,快速字符串存储 (rep stos / rep movs) 也可以使用无 RFO 写协议,尽管它们将数据留在缓存层次结构中。这是非常有趣的事情。这是您个人研究的结果吗?我同意这一点,因为使用 erms 的 L2-绑定 (128KB) 内存复制例程在我的笔记本电脑上平均优于 AVX2 的 20%。据我所知,SOM 中没有记录 rms 微码生成的存储 uops 使用了什么样的协议。 - Some Name
2
@SomeName:我的来源是Andy Glew在Intel工作时设计P6快速字符串实现的Stack Overflow评论。我在this answer中引用了他的评论。ERMSB是一个单独的功能,允许这些存储器乱序提交。我假设即使在后来的P6家族和Sandybridge之前没有ERMSB,快速字符串也超越了仅使用宽加载/存储,包括多核芯片,但我不确定无RFO存储特性是否始终存在。 - Peter Cordes
1
@PeterCordes SKX有一个“内存目录”(与DRAM中的缓存行数据共存),用于确定它是否需要向其他插槽发送嗅探并确定是否需要等待嗅探响应返回。 “嗅探过滤器”是一个完全不同的子系统,用于在没有包含式L3的情况下进行芯片内一致性。嗅探过滤器本质上是包含式L3的标记,但没有容纳数据的空间。“内存目录”功能早在IVB和BDW中就已经包括,但现在是SKX唯一支持的模式。由于独占式L3,嗅探过滤器是新的。 - John D McCalpin

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