将相同值再次写入缓存行的性能

performance for writing the same value again into cache line

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

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

与仅此代码相反:

matrix[t] = 0;

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

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

典型的 CPUs(或至少一些)优化了关于这个案例的任何事情吗?

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

它通常很少见,并且只值得热共享变量的额外缓存访问周期,因此微体系结构默认执行它没有意义。大多数商店不是共享变量,并且在商店缓冲区内它不知道哪些商店是共享变量。


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

如果其他线程读取它的时间比您上次写入它的时间更近,那么最好避免写入共享变量,因为它总是使所有其他副本无效以获取本地 CPU' s 行进入修改状态。

在某些情况下,负载 + 分支预测错误的成本可能高于节省的成本,尤其是在预测不佳的情况下。 (推测性 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 的成本更高,因此值得的可能性可能更高。