缓存行重复写入相同值的性能

5

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

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

与仅有这段代码相反:
matrix[t] = 0;

我想这段代码是这样编写的,以减少CPU中的内存带宽。在典型的CPU上(当值很可能已经为0时),这是一种好的优化方法吗?为什么?
这对MESI状态意味着什么:如果我将相同的值写回缓存行(写入但不修改),是否会从共享状态转换到修改状态?或者这对于CPU来说太复杂了,无法检测到?
典型的CPU(或至少某些CPU)是否在优化此情况的任何内容?

读取速度可能比写入慢(处理器必须等待往返),因此我不认为这是一种优化。(然后通过执行写操作来增加带宽,如果读取产生了某些内容)。写操作不一定会增加DRAM/缓慢内存带宽,直到从缓存中驱逐出去才会减慢所有人的速度。如果写操作可以通过/绕过缓存并且不会导致空间被缓存,那么这就是一种优化。(写入穿透)。 - old_timer
1个回答

4
据我所知,没有x86微架构尝试通过在共享MESI状态下读取并检查值是否匹配来从存储缓冲区提交存储到L1D。
这通常很少见,只有对于热共享变量才值得额外的缓存访问周期,因此默认情况下不应由微架构执行。大多数存储都不是针对共享变量的,在存储缓冲区内它不知道哪些存储是针对共享变量的。
在值得进行此操作的情况下(即有时针对共享变量),您必须使用类似问题中的if()代码自己执行。那正是该代码的目的,是可以实现的。
如果存在其他线程最近读取过共享变量,则避免编写共享变量是一个好主意,因为它总是使所有其他副本无效以将本地CPU的行置为修改状态。
在某些情况下,加载+分支错误预测的成本可能高于节省成本,特别是如果预测不良。 (甚至可以在检测到错误预测之前使规范性RFO无效其他副本。当然,规范性存储实际上无法提交到L1D,但是据我所知,拥有权的读取可以发生。)
作为另一个例子,在自旋锁的重试循环中,您始终希望在纯负载(+pause)上旋转,而不是在xchg上旋转。在xchglock cmpxchg上旋转将继续攻击该缓存行并延迟实际解锁它的代码。
甚至Intel的优化手册也建议在TSX章节中进行此优化,以避免不必要的存储从而减少其他访问共享变量的线程中的事务中止。
// Example 12-1
state = true; // updates every time
var |= flag;

vs.

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

在使用TSX时,事务中止的成本甚至比MESI多等待的成本更高,因此其价值可能更高。

1
我在想编译器是否可以合法地将这个检查-设置循环转换为0的memset(即消除检查,因为这可能是有利的,除了你提到的共享的不寻常情况)。然而,看起来他们并没有这样做。当然,您不能对未知来源的任意int *执行此操作,因为它可能已被定义为const,因此写入(但不读取)将是UB。我认为数据竞争规则也使得在原始程序中没有写入的地方引入写入变得非常困难。 - BeeOnRope

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接