【问题标题】:Reducing bus traffic for cache line invalidation减少高速缓存行失效的总线流量
【发布时间】:2020-10-18 05:48:02
【问题描述】:

共享内存多处理系统通常需要为缓存一致性生成大量流量。 Core A 写入缓存。 Core 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 指示给定的缓存行将永远不会被任何其他线程感兴趣?

我对任何系统的答案都感兴趣,尤其是对于 x64、ARM 或 RISC-V 上最常见的 Linux 当前/未来服务器配置。

【问题讨论】:

    标签: multithreading cpu-architecture cpu-cache memory-barriers memory-model


    【解决方案1】:

    真正的 CPU 不使用共享总线;流量通过 L3 缓存,其标签用作窥探过滤器(尤其是在单插槽 Intel 芯片中)。或在其他微架构上节省流量的类似方法。你是对的,当你扩展到许多核心时,实际上向每个其他核心广播一条消息对于功率和性能来说都是非常昂贵的。 共享总线只是 MESI 等协议的简单心智模型,而不是现代 CPU 中的真正实现。例如,参见 What cache coherence solution do modern x86 CPUs use?

    使用 write-allocate 的回写式高速缓存需要先读取高速缓存行,然后再将其存储到其中,因此它们具有该行其他部分的原始数据。这种读取,当由写入触发时,称为“读取所有权”(RFO),以使线路进入 MESI 独占状态(可以在没有外部流量的情况下转换为脏修改)。 RFO 包括失效。

    如果初始访问是只读的,则该行通常会像 RFO 一样以独占状态到达,如果没有其他内核有缓存副本(即它在 L3(最后一级)缓存中丢失)。这意味着对于读取一些私有数据然后对其进行修改的常见模式,流量保持在最低限度。

    我认为,多插座系统必须探听另一个插座或咨询探听过滤器以确定这一点,但最对功率/能源敏感的系统是移动的(始终是单插座)。


    有趣的事实:Skylake-X 之前的英特尔 2 插槽 Xeon 芯片(例如 E5 ...-v4)没有针对插槽之间的流量的窥探过滤器,并且只是通过 QPI 链路对另一个插槽进行垃圾邮件窥探。 E7 CPU(能够用于四核和更大的系统)具有专用的监听过滤器缓存来跟踪热线的状态,以及足够的 QPI 链接来交叉连接更多的套接字。来源:John McCalpin's post on an Intel forum,虽然我还没有找到很多其他数据。也许约翰正在考虑像 Core2 / Nehalem Xeons 这样的早期系统,英特尔确实谈到了具有窥探过滤器,例如 https://www.intel.ca/content/dam/doc/white-paper/quick-path-interconnect-introduction-paper.pdf 将 QPI 与其早期设置进行比较。并且有一些关于可以权衡延迟与吞吐量的窥探模式的更多细节。也许英特尔只是不以同样的方式使用术语“窥探过滤器”。

    有没有办法反过来做,向 CPU 指示给定的缓存行将永远不会被任何其他线程感兴趣?

    如果你有一个缓存写入协议,将存储数据与失效结合起来,你可以跳过 RFO。 例如x86 具有绕过缓存的 NT 存储,并且显然是快速字符串存储 (rep stos / rep movs),甚至在 ERMSB 还可以使用无 RFO 写入协议 (at least in P6, according to Andy Glew who designed it) 之前,即使它们将数据留在缓存中等级制度。但是,这仍然需要使其他缓存失效,除非该内核已经拥有处于 E 或 M 状态的行。 Enhanced REP MOVSB for memcpy

    某些 CPU 确实有一些 scratchpad memory,它是每个内核真正私有的。它根本不共享,因此不需要或不可能进行显式刷新。请参阅 Bandwidth 博士在 Can you directly access the cache using assembly? 上的回答 - 这在 DSP 上显然很常见。


    但除此之外,通常不会,CPU 不提供将部分内存地址空间视为不连贯的方法。一致性是 CPU 不想让软件禁用的保证。 (也许是因为它可能会造成安全问题,例如,如果某些旧写入可能最终在操作系统校验和之后在文件数据页面中变得可见,但在 DMA 到磁盘之前,非特权用户空间可能会导致像 BTRFS 或 ZFS 这样的校验和 FS,以查看它在 mmap(PROT_WRITE|PROT_READ, MAP_SHARED) 上执行的文件中的坏块。)

    通常,内存屏障的工作原理是让当前核心等待,直到存储缓冲区排入 L1d 缓存(即先前的存储已成为全局可见),所以如果您允许非连贯的 L1d,那么一些冲洗它需要其他机制。 (例如 x86 clflushclwb 强制写回外部缓存。)

    为大多数软件创建利用这一点的方法很困难;例如假设您可以获取本地变量的地址并将其传递给其他线程。即使在单线程程序中,任何指针都可能来自mmap(MAP_SHARED)。因此,您不能默认将堆栈空间映射为非连贯或类似的东西,并且编译程序以使用额外的刷新指令,以防它们获得指向非连贯内存的指针,毕竟这将完全失败整个事情的目的。

    所以这不值得追求的部分原因是,为了提高效率,堆栈中的所有东西都必须关心,这更加复杂。 Snoop 过滤器和基于目录的一致性足以解决问题,而且总体上比期望每个人都针对这个低级功能优化他们的代码要好得多!

    【讨论】:

    • 甚至在 ERMSB 也可以使用无 RFO 写入协议之前,显然是快速字符串存储 (rep stos / rep movs),即使它们将数据留在缓存层次结构中 这是一件非常有趣的事情。这是您个人研究的结果吗?我同意这一点,因为在我的笔记本电脑上使用 erms 的 L2-bound (128KB) 内存复制例程平均优于 AVX2 20%。 AFAIK 没有记录在 SOM 中使用哪种协议来存储由rms 微码生成的微指令。
    • @SomeName:我的来源是 Andy Glew 的 Stack Overflow 评论,他在英特尔从事 PPro 工作时设计了 P6 快速字符串实现。我在this answer 引用了他的cmets。 ERMSB 是一个单独的功能,允许这些存储无序提交。我认为即使在后来的 P6 系列和 ERMSB 之前的 Sandybridge 中,快速字符串也不仅仅是使用宽负载/存储,包括多核芯片,但我不确定无 RFO 存储功能是否始终存在。跨度>
    • @PeterCordes SKX 有一个“内存目录”(与 DRAM 中的缓存行数据位于同一位置),用于确定是否需要向其他套接字发送监听以及是否需要等待窥探响应返回。 “窥探滤波器”是一个完全不同的子系统,用于在没有包含 L3 的情况下实现片上一致性。 snoop 过滤器本质上是一个包容性 L3 的标签,没有空间来保存数据。 “内存目录”功能已包含在早期(IVB 和 BDW),但现在是 SKX 支持的唯一模式。 Snoop Filter 是新的(由于独家 L3)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-04
    • 2011-12-13
    • 1970-01-01
    • 2021-03-20
    相关资源
    最近更新 更多