Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Assembly cmpxchg是否在失败时写入目标缓存线?如果不是,它是否比xchg更适合spinlock?_Assembly_X86_Cpu Cache_Micro Optimization_Compare And Swap - Fatal编程技术网

Assembly cmpxchg是否在失败时写入目标缓存线?如果不是,它是否比xchg更适合spinlock?

Assembly cmpxchg是否在失败时写入目标缓存线?如果不是,它是否比xchg更适合spinlock?,assembly,x86,cpu-cache,micro-optimization,compare-and-swap,Assembly,X86,Cpu Cache,Micro Optimization,Compare And Swap,我假设简单的自旋锁不会进入操作系统,等待回答这个问题 我发现简单的自旋锁通常使用lock xchg或lock bts来实现,而不是lock cmpxchg 但是如果期望值不匹配,cmpxchg是否避免写入值?那么,使用cmpxchg,失败的尝试不是更便宜吗 或者cmpxchg是否写入数据并使其他内核的缓存线失效(即使出现故障) 这个问题类似于,但它是针对cmpxchg的,不是一般的。我做了一些测试。虽然是非常合成的,但在锁下做了一点,并测量了非常有争议的场景的吞吐量 到目前为止,未观察到lock

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

我发现简单的自旋锁通常使用
lock xchg
lock bts
来实现,而不是
lock cmpxchg

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

或者
cmpxchg
是否写入数据并使其他内核的缓存线失效(即使出现故障)


这个问题类似于,但它是针对cmpxchg的,不是一般的。

我做了一些测试。虽然是非常合成的,但在锁下做了一点,并测量了非常有争议的场景的吞吐量

到目前为止,未观察到
lock bts
xchg
lock cmpxchg
之间差异的稳定效应

然而,其他东西也有一些影响:

  • 内部
    load
    循环肯定很有帮助,无论是否有
    pause
  • 循环中的一个
    暂停
    ,无论有无加载循环,都是有帮助的
  • 加载循环不仅仅是暂停
  • 通过应用《英特尔64和IA-32体系结构优化参考手册》(见下文)中的“改进版”,可获得最佳效果
  • 从负载而不是RMW/CAS开始具有争议性的效果:它有助于不暂停的测试,但会降低暂停测试的性能

建议使用
暂停

例2-4。增加退避的争用锁示例显示基线版本:

/*******************/
/*Baseline Version */
/*******************/
// atomic {if (lock == free) then change lock state to busy}
while (cmpxchg(lock, free, busy) == fail)
{
 while (lock == busy)
 {
 __asm__ ("pause");
 }
}
和改进版:

/*******************/
/*Improved Version */
/*******************/
int mask = 1;
int const max = 64; //MAX_BACKOFF
while (cmpxchg(lock, free, busy) == fail)
{
 while (lock == busy)
 {
   for (int i=mask; i; --i){
     __asm__ ("pause");
   }
   mask = mask < max ? mask<<1 : max;
 }
}

然而,它并没有在等待版本中实现指数增长的
pause
。它只需暂停一次即可执行少量加载,然后进入操作系统等待。

在大多数或所有当前的英特尔x86处理器上,将cmpxchg锁定到内存类型为WB且完全包含在单个L1D缓存线中的位置,如下所示:

  • 向L1D发出锁定读取请求,使目标行处于锁定的独占缓存一致性状态,并将请求的字节作为输入提供给其中一个执行端口以执行比较。(从P6开始支持缓存锁定。)处于锁定状态的行不能因任何原因而无效或逐出
  • 执行相等的比较
  • 无论结果如何,向L1D发出解锁写入请求,这会将缓存线的状态更改为修改并解锁缓存线,从而允许其他访问或一致性请求替换或使缓存线无效
第一步和最后一步可以通过使用特定的性能事件或基于延迟的度量进行经验观察。一种方法是分配一个大的原子变量数组,然后在该数组上的循环中执行
lock cmpxchg
。锁定读取请求类型是RFO请求类型之一。因此,在大多数微体系结构上可靠的
L2_TRANS.RFO事件(或等效事件)可用于测量对L2的锁读取次数。(
L2_-TRANS.RFO
统计需求RFO,因此最好关闭硬件预取器以避免在L2中出现不必要的点击。这也适用于
L2_-RQSTS.RFO.*

还有用于测量写回次数的事件,例如
L2_TRANS.L1D_WB
L2_TRANS.L2_WB
,以及其他事件。不幸的是,这些事件中的许多以及跨多个微阵列的事件要么计数不足,要么计数过多,要么计数准确,但不一定全部/仅脏缓存线写回。因此,它们更难推理,而且通常不可靠

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

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


Intel有一个建议对最后一个进行优化的解决方案,其中内核乐观地假设没有锁争用,并向目标行发出推测性正常负载。如果该线路不存在于任何其他物理核心中,则该线路将在请求核心中处于独占状态。然后,当锁定的指令执行并发出锁定读取请求时,该行有望仍处于独占状态,在这种情况下,锁定指令的总延迟将减少。我不知道是否有处理器实现了这种优化。如果它被实现,那么
L2_TRANS.RFO
事件的数量将远小于锁定的行数。

我认为所有原子rmw都有效地算作存储,包括
lock cmpxchg
。至少在历史上(对于外部可见的效果),说“处理器从不专业
RtlTryAcquireSRWLockExclusive:
00007FFA86D71370  lock bts    qword ptr [rcx],0  
00007FFA86D71376  setae       al  
00007FFA86D71379  ret