cmpxchg 是否会在失败时写入目标缓存行?如果不是,对于自旋锁来说它比 xchg 更好吗?

Ale*_*iev 5 x86 assembly micro-optimization compare-and-swap cpu-cache

我假设简单的自旋锁不会进入操作系统等待这个问题的目的。

我发现简单的自旋锁通常使用lock xchgorlock bts代替 来实现lock cmpxchg

cmpxchg但是如果期望不匹配,是否会避免写入该值?那么失败的尝试不是更便宜吗cmpxchg

或者即使cmpxchg发生故障也会写入数据并使其他核心的缓存线无效?

这个问题类似于什么具体将 x86 缓存行标记为脏 - 任何写入,还是需要显式更改?,但它是特定的cmpxchg,而不是普遍的。

Had*_*ais 4

在大多数或所有当前的 Intel x86 处理器上,lock cmpxchg对内存类型为 WB 且完全包含在单个 L1D 高速缓存行中的位置的执行如下:

  • 向 L1D 发出锁定读取请求,使目标行处于锁定独占高速缓存一致性状态,并将请求的字节作为输入提供给执行端口之一以执行比较。(从 P6 开始支持高速缓存锁定。)处于锁定状态的行不能因任何原因而失效或被逐出。
  • 执行相等比较。
  • 无论结果是什么,向 L1D 发出解锁写入请求,这会将缓存行的状态更改为“已修改”并解锁该行,从而允许其他访问或一致性请求替换该行或使该行无效。

可以使用某些性能事件或基于延迟的测量来凭经验观察第一步和最后一步。一种方法是分配一个大的原子变量数组,然后lock cmpxchg在该数组上循环执行。锁读请求类型是RFO请求类型之一。因此,L2_TRANS.RFO在大多数微架构上可靠的事件(或等效事件)可用于测量 L2 的锁读取次数。(L2_TRANS.RFO计算需求 RFO,因此最好关闭硬件预取器以避免 L2 中不必要的命中。这也适用于L2_RQSTS.RFO_*。)

还有一些用于测量写回次数的事件,例如L2_TRANS.L1D_WBL2_TRANS.L2_WB、 等。不幸的是,许多这些事件和跨许多微体系结构要么计数不足,要么计数过多,或者它们计数准确但不一定是所有/仅脏缓存行写回。因此,他们更难以推理,而且通常不可靠。

更好的方法是lock cmpxchg在特定物理核心上的阵列的一个部分上执行,然后将线程迁移到另一个物理核心(在同一个 L3 共享域中)并执行一个循环,在该循环中读取该部分的元素(正常读)。如果lock cmpxchg指令将目标行置于 M 状态,则来自同一 L3 共享域中的另一个物理核心的读取请求应该在 L3 中命中,并且也会在执行的核心的私有缓存中命中修改lock cmpxchg。这些事件可以使用OFFCORE_RESPONSE.DEMAND_DATA_RD.L3_HIT.HITM_OTHER_CORE(或等效的方法)进行计数,这在大多数/所有微体系结构上都是可靠的。

锁定指令是一项昂贵的操作,原因有以下三个:(1) 需要使行处于独占状态,(2) 使行变脏(可能不必要),并且太多的写回会对执行时间产生重大影响,甚至更是如此当它们最终从长时间的读取请求中窃取主内存带宽时,当写入持久内存时更是如此,并且(3)它们在架构上进行序列化,这使得指令位于关键路径上。

英特尔拥有一项专利,提出对最后一个优化,其中核心乐观地假设不存在锁争用并向目标线发出推测性正常负载。如果该线路不存在于任何其他物理核心中,则该线路在请求核心中将处于独占状态。然后,当锁定指令执行并发出锁定读取请求时,该行有望仍处于独占状态,在这种情况下,锁定指令的总延迟将减少。我不知道是否有处理器实现了这种优化。如果它被实现,事件的数量L2_TRANS.RFO将比锁定的行数少得多。