原子增量正在刷新其他缓存行(提供的代码和结果)



设置:Intel Ivy Bridge Core i7,在64位模式下编译,MSVC(2012)和win7 64位。

我试图理解原子增量是否会导致缓存丢失。

我设置了一个测试,其中原子变量和另一个变量在同一缓存行中,而不是在同一缓存行中,然后比较缓存缺失。代码和结果如下:

结果

不同的缓存行:

  • 原子增量没有L1缓存丢失
  • d.a的增量都有40-50%的L1缓存丢失。

相同的缓存行

  • 递增d.a没有缓存丢失
  • 递增原子遇到100% L1缓存丢失
有人能解释一下吗?我期待当原子在与d.a相同的缓存线时,d.a将遭受100%的缓存丢失,当它们在不同的缓存线时,d.a不会受到影响。
#include <atomic>
#include <iostream>
#include <iomanip>
#include <vector>
//Structure to ensure not in same cache line
__declspec(align(64)) struct S{
  volatile double a,b,d,c,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t;
  volatile std::atomic<short> atom;
};
//Structure to ensure same cache line
/*__declspec(align(64)) struct S{
   volatile std::atomic<short> atom;
   volatile short a;
};*/

int main(){
    volatile S d;
    for(long long i=0; i<1000000000; i++){
        d.a++;
        d.atom++;
        d.a++;
    }
}

UPDATE这里是一些asm:

    /* _Atomic_fetch_add_2, _Atomic_fetch_sub_2 */
inline _Uint2_t _Fetch_add_seq_cst_2(volatile _Uint2_t *_Tgt, _Uint2_t _Value)
    {   /* add _Value to *_Tgt atomically with
 mov         word ptr [_Tgt],dx  
 mov         qword ptr [rsp+8],rcx  
 push        rdi  
            sequentially consistent memory order */
    return (_INTRIN_SEQ_CST(_InterlockedExchangeAdd16)((volatile short *)_Tgt, _Value));
 movzx       eax,word ptr [_Value]  
 mov         rcx,qword ptr [_Tgt]  
 lock xadd   word ptr [rcx],ax  
    }
 pop         rdi  

看看这个序列:

for(long long i=0; i<1000000000; i++){
    d.a++;
    d.atom++;
    d.a++;
}
我们可以将

重写为(大致):

for(long long i=0; i<1000000000 / 4; i+=4){
    d.a++;
    d.atom++;
    d.a++;
    d.a++;
    d.atom++;
    d.a++;
    d.a++;
    d.atom++;
    d.a++;
    d.a++;
    d.atom++;
    d.a++;
}

我可以继续并进一步展开循环,但很明显,在d.atom++之后有两个d.a++

换句话说,基于在循环结束时为d.a++获取下一次循环迭代的数据的d.a++,您应该期望d.a++上大约50%的缓存丢失。任何与此不一致的地方都是测量错误(我认为缓存缺失是通过统计来测量的,而不是精确的行上的精确步骤)。

在d.a和d.a atom位于不同缓存线上的情况下,显然d.atom++获得了该特定地址上的所有缓存缺失,这解释了在这种情况下100%的数字。

我不能100%确定锁定操作的定义("锁定操作"在x86领域=原子)需要缓存刷新,但它肯定需要"独占访问",这意味着所有其他CPU(内核)将需要被告知"您现在必须从缓存中刷新此数据的任何副本"。从您的测试中可以看出,至少在这种型号的处理器上,这相当于"刷新本行的所有缓存",包括当前保存数据的缓存。

参见SO讨论什么是内存围栏?这就是现代英特尔cpu实现锁指令的方式。内存屏障强制将数据完全写入内存,然后在下次访问时重新读取。在[ref to A] [barrier] [ref to A]的情况下,CPU不能对第二个引用做任何聪明的预取。所有屏障指令都以相同的方式操作,尽管有些指令允许您明确地限制为左或右屏障模式。

执行barrier指令会导致性能下降。试图理解这种命中的特征会因CPU体系结构而异,并且在H/W中执行此操作的算法的内部不是英特尔将详细发布的内容。

最新更新