12

这个问题专门针对现代 x86-64 缓存一致性架构 - 我很欣赏其他 CPU 上的答案可能会有所不同。

如果我写入内存,MESI 协议要求先将缓存行读入缓存,然后在缓存中修改(将值写入缓存行,然后将其标记为脏)。在较旧的 write-though 微架构中,这将触发缓存行被刷新,在回写下,被刷新的缓存行可能会延迟一段时间,并且在两种机制下都可能发生一些写组合(更可能是写回) . 而且我知道这如何与访问同一数据缓存行的其他内核交互 - 缓存侦听等。

我的问题是,如果存储与缓存中已经存在的值精确匹配,如果没有一个位被翻转,是否有任何英特尔微架构注意到这一点并且不会将该行标记为脏,从而可能避免该行被标记为排他性的,以及在某些时候会出现的写回内存开销?

当我对更多循环进行矢量化时,我的矢量化操作组合原语不会显式检查值的变化,并且在 CPU/ALU 中这样做似乎很浪费,但我想知道底层缓存电路是否可以在没有显式编码的情况下做到这一点(例如存储微操作或缓存逻辑本身)。随着跨多个内核的共享内存带宽越来越成为资源瓶颈,这似乎是一个越来越有用的优化(例如,重复对同一内存缓冲区进行归零——如果它们已经从 RAM 中重新读取值,我们就不会重新读取它们)在缓存中,但强制写回相同的值似乎很浪费)。写回缓存本身就是对此类问题的一种认可。

我可以礼貌地要求保留“理论上”或“这真的没关系”的答案吗?我知道内存模型是如何工作的,我正在寻找的是关于如何编写相同值的确凿事实(而不是避免存储)将影响内存总线的争用,您可以放心地假设这是一台运行多个工作负载的机器,这些工作负载几乎总是受内存带宽的限制。另一方面,对芯片不这样做的确切原因的解释(我悲观地假设他们不这样做)将是有启发性的......

更新: 这里有一些符合预期的答案https://softwareengineering.stackexchange.com/questions/302705/are-there-cpus-that-perform-this-possible-l1-cache-write-optimization但仍然有很多猜测“它一定很难,因为它没有完成”,并说在主 CPU 内核中这样做会很昂贵(但我仍然想知道为什么它不能成为实际缓存逻辑本身的一部分)。

更新(2020 年): Travis Downs 发现了硬件商店消除的证据,但似乎仅针对零且仅在数据未命中 L1 和 L2 的情况下,即便如此,并非在所有情况下都是如此。强烈推荐他的文章,因为它更详细...... https://travisdowns.github.io/blog/2020/05/13/intel-zero-opt.html

更新(2021 年): Travis Downs 现在发现证据表明这种零存储优化最近已在微码中被禁用......更多细节来自源本人 https://travisdowns.github.io/blog/2021/06/17 /rip-zero-opt.html

4

3 回答 3

7

目前,x86(或任何其他 ISA,据我所知)的实现不支持优化静默存储。

对此已有学术研究,甚至有一项关于“消除共享内存缓存一致性协议中的静默存储失效传播”的专利。(如果您对更多内容感兴趣,请搜索“静默存储”缓存。)

对于 x86,这会干扰 MONITOR/MWAIT;一些用户可能希望监视线程在静默存储中唤醒(可以避免失效并添加“触及”一致性消息)。(目前 MONITOR/MWAIT 具有特权,但将来可能会改变。)

同样,这可能会干扰事务内存的一些巧妙使用。如果将内存位置用作保护以避免显式加载其他内存位置,或者在支持此类的架构中(例如在 AMD 的高级同步工具中),则从读取集中删除受保护的内存位置。

(Hardware Lock Elision 是静默 ABA 存储消除的一个非常受限的实现。它具有显式请求检查值一致性的实现优势。)

在性能影响/设计复杂性方面也存在实施问题。这将禁止避免为所有权而读取(除非静默存储消除仅在缓存行已处于共享状态时才有效),尽管目前也未实现为所有权而读取的避免。

对静默存储的特殊处理也会使内存一致性模型的实现复杂化(可能尤其是 x86 的相对强大的模型)。这也可能会增加对一致性失败的推测进行回滚的频率。如果静默存储仅支持 L1-present 行,则时间窗口将非常小,并且回滚极为罕见;存储到 L3 或内存中的缓存行可能会将频率增加到非常罕见,这可能使其成为一个值得注意的问题。

缓存行粒度的静默也比访问级别的静默少见,因此避免的无效次数会更少。

额外的缓存带宽也是一个问题。目前,英特尔仅在 L1 缓存上使用奇偶校验,以避免在小写入时需要读取-修改-写入。要求每次写入都进行读取以检测静默存储将具有明显的性能和功耗影响。(此类读取可能仅限于共享高速缓存行,并且可能会随机执行,利用没有完全高速缓存访​​问利用的周期,但这仍然会产生电力成本。)这也意味着如果读取-修改-写入支持,则此成本将下降已经存在 L1 ECC 支持(该功能会让一些用户满意)。

我对消除无声商店的了解并不多,因此可能还有其他问题(和解决方法)。

随着性能改进的许多唾手可得的成果已经被采纳,更困难、更少有益和不太通用的优化变得更具吸引力。由于静默存储优化随着更高的内核间通信变得更加重要,并且随着更多内核用于处理单个任务,内核间通信将增加,这样的价值似乎可能会增加。

于 2017-11-21T17:26:50.480 回答
6

可以在硬件中实现,但我认为没有人这样做。对每个商店都这样做会花费缓存读取带宽,或者需要额外的读取端口,并使流水线变得更加困难。

您将构建一个执行读取/比较/写入周期而不是仅写入的缓存,并且可以有条件地将行保留在 Exclusive 状态而不是 Modified (的MESI)。这样做(而不是在仍然共享时进行检查)仍然会使该行的其他副本无效,但这意味着与内存排序没有交互。当核心拥有缓存行的独占所有权时,(静默)存储变得全局可见,就像它已经翻转到修改然后通过回写到 DRAM 回到独占一样。

读取/比较/写入必须以原子方式完成(您不能丢失读取和写入之间的缓存线;如果发生这种情况,比较结果将是陈旧的)。这使得管道数据从存储队列提交到 L1D 变得更加困难。


在多线程程序中,将其作为软件中仅针对共享变量的优化是值得的。

避免使其他人的缓存失效可以使它值得转换

shared = x;

进入

if(shared != x)
    shared = x;

我不确定这里是否存在内存排序问题。显然,如果shared = x从未发生过,则没有发布序列,因此您只有获取语义而不是发布。但是,如果您存储的值通常是已经存在的值,那么将其用于订购其他东西都会出现 ABA 问题。

IIRC,Herb Sutter 在他的atomic Weapons: The C++ Memory Model and Modern Hardware talk的第 1 部分或第 2 部分中提到了这种潜在的优化。(几个小时的视频)

对于共享变量以外的任何东西,在软件中这样做当然太昂贵了,其中写入它们的成本是其他线程中的许多延迟周期(缓存未命中和内存顺序错误推测机器清除:什么是延迟和吞吐量成本生产者-消费者在超兄弟姐妹与非超兄弟姐妹之间共享内存位置?


相关:有关 x86 内存带宽的更多信息,请参阅此答案,尤其是 NT 与非 NT 存储的东西,以及“延迟绑定平台”,了解为什么多核 Xeon 上的单线程内存带宽低于四核核心,即使来自多个核心的聚合带宽更高。

于 2017-11-22T00:36:31.127 回答
6

我发现有证据表明,英特尔的一些现代 x86 CPU,包括 Skylake 和 Ice Lake 客户端芯片,可以在至少一种特定情况下优化冗余(静默)存储:

  • 全零高速缓存行被更多零完全或部分覆盖。

也就是说,“零对零”的场景。

例如,此图表显示了在 Ice Lake 上使用 0 或 1 的 32 位值归档的情况下的性能(圆圈,在左轴上测量)和相关性能计数器:

冰湖填充性能

一旦该区域不再适合 L2 缓存,写入零就有一个明显的优势:填充吞吐量几乎提高了 1.5 倍。在零的情况下,我们还看到来自 L2 的驱逐几乎都不是“静默”的,这表明不需要写出脏数据,而在另一种情况下,所有驱逐都是非静默的。

有关此优化的一些杂项细节:

  • 它优化了脏缓存行的回写,而不是仍然需要发生的 RFO(实际上,可能需要读取来决定可以应用优化)。
  • 它似乎发生在 L2 或 L2 <-> L3 接口周围。也就是说,对于适合 L1 或 L2 的负载,我没有找到这种优化的证据。
  • 因为优化在缓存层次结构的最内层之外的某个点生效,所以不必写零来利用:仅当行写回 L3 时才包含全零就足够了。因此,从全零行开始,您可以执行任意数量的非零写入,然后对整行1进行最终的零写入,只要该行同时不逃逸到 L3。
  • 优化具有不同的性能影响:有时优化是基于对相关性能计数的观察进行的,但吞吐量几乎没有增加。其他时候影响可能非常大。
  • 我没有在 Skylake 服务器或更早的英特尔芯片中找到效果的证据。

我在这里更详细地写了这个,还有一个冰湖的附录,在这里更强烈地展示了这种效果。

2021 年 6 月更新:出于安全原因,英特尔提供的最新 CPU 微码版本已禁用此优化(详情)。


1或者,至少用零覆盖行的非零部分。

于 2020-07-28T21:43:13.167 回答