真正的 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 clflush 或 clwb 强制写回外部缓存。)
为大多数软件创建利用这一点的方法很困难;例如假设您可以获取本地变量的地址并将其传递给其他线程。即使在单线程程序中,任何指针都可能来自mmap(MAP_SHARED)。因此,您不能默认将堆栈空间映射为非连贯或类似的东西,并且编译程序以使用额外的刷新指令,以防它们获得指向非连贯内存的指针,毕竟这将完全失败整个事情的目的。
所以这不值得追求的部分原因是,为了提高效率,堆栈中的所有东西都必须关心,这更加复杂。 Snoop 过滤器和基于目录的一致性足以解决问题,而且总体上比期望每个人都针对这个低级功能优化他们的代码要好得多!