【问题标题】:performance for writing the same value again into cache line将相同值再次写入缓存行的性能
【发布时间】:2018-03-26 17:47:40
【问题描述】:

我有时会看到这样的优化代码:

if (matrix[t] != 0) {
    matrix[t] = 0;
}

与仅此代码相反:

matrix[t] = 0;

我认为这样编写代码是为了减少 CPU 中的内存带宽。这是对典型 CPU 的良好优化(当值可能已经为 0 时)?为什么?

这对 MESI 状态意味着什么:是否存在状态转换,例如如果我将相同的值写回缓存行(写入但不修改),共享到修改?或者这对于 CPU 来说是否太复杂而无法检测到?

典型的 CPU(或至少一些)是否针对这种情况进行了优化?

【问题讨论】:

  • 读取可能比写入慢(处理器必须等待往返),所以我不认为这是一种优化。 (然后如果读取产生了某些东西,则通过写入来增加更多带宽)。写入不一定会增加 DRAM / 慢速内存带宽,从而减慢每个人的速度,直到从缓存中驱逐。如果写入会通过/绕过缓存并且不会导致空间被缓存,那么确保这是一种优化。 (写完)。

标签: caching optimization cpu x86-64 mesi


【解决方案1】:

AFAIK,没有 x86 微架构尝试通过在仍处于 Shared MESI 状态时读取并检查值是否匹配来将存储从存储缓冲区提交到 L1D。

这通常很少见,而且只值得为热共享变量增加额外的缓存访问周期,因此默认情况下微架构这样做是没有意义的。大多数存储共享变量,并且在存储缓冲区中它不知道哪些存储是共享变量。


在值得这样做的情况下(即有时对于共享变量),您必须自己使用问题中的 if() 之类的代码来完成。这正是该代码的用途,是的,它可以是一个胜利。

如果其他线程很可能比您上次写入共享变量的时间更晚,则避免写入共享变量是个好主意,因为它总是使所有其他副本无效以使本地 CPU 的行进入修改状态。

在某些情况下,负载 + 分支错误预测的成本可能高于节省成本,尤其是在预测不佳的情况下。 (推测性 RFO 甚至可能在检测到错误预测之前使其他副本无效。 当然,投机存储实际上不能提交到 L1D,但所有权的读取可能会发生 AFAIK。)

作为另一个例子,在自旋锁的重试循环中,您总是希望在纯负载 (+ pause) 上旋转,而不是在 xchg 上旋转。在xchglock cmpxchg 上旋转将继续敲击该缓存行并延迟实际解锁它的代码。


Intel's optimization manual 甚至在 TSX 章节中建议了这种优化,以通过避免不必要的存储来减少访问共享变量的其他线程中的事务中止。

// Example 12-1
state = true; // updates every time
var |= flag;

vs.

if (state != true) state = true;
if (!(var & flag)) var |= flag;

对于 TSX,交易中止的成本甚至比额外等待 MESI 的成本更高,因此值得它的机会可能更高。

【讨论】:

  • 我想知道编译器是否可以合法地将这个 check-then-set 循环转换为 0 的 memset(即,消除检查,因为这可能是有利可图的,除非在不寻常的共享情况下正如你提到的)。但是,它看起来不像they do。您当然不能在出处未知的任意int * 上执行此操作,因为它可能已定义为const,因此写入(但不是读取)将是UB。我认为数据竞争规则也使得引入原始程序中没有的写入变得非常困难。
猜你喜欢
  • 2017-10-16
  • 2022-10-26
  • 2014-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多