Performance 原子运行成本

Performance 原子运行成本,performance,atomic,cpu-architecture,lock-free,Performance,Atomic,Cpu Architecture,Lock Free,原子操作(比较和交换或原子增减)的成本是多少?它消耗多少周期?它会暂停SMP或NUMA上的其他处理器,还是会阻止内存访问? 它会在坏序CPU中刷新重新排序缓冲区吗 缓存将受到什么影响 我对现代的、流行的CPU感兴趣:x86、x86\u 64、PowerPC、SPARC、安腾。在基于总线的SMP上,原子前缀LOCK会断言(打开)总线线信号LOCK。它将禁止总线上的其他CPU/设备使用它 Ppro和P2书籍第244-246页 锁定的指令正在序列化、同步操作。。。。 /关于无序/锁定RMW/read

原子操作(比较和交换或原子增减)的成本是多少?它消耗多少周期?它会暂停SMP或NUMA上的其他处理器,还是会阻止内存访问? 它会在坏序CPU中刷新重新排序缓冲区吗

缓存将受到什么影响


我对现代的、流行的CPU感兴趣:x86、x86\u 64、PowerPC、SPARC、安腾。

在基于总线的SMP上,原子前缀
LOCK
会断言(打开)总线线信号
LOCK
。它将禁止总线上的其他CPU/设备使用它

Ppro和P2书籍第244-246页

锁定的指令正在序列化、同步操作。。。。 /关于无序/锁定RMW/read modify write=原子本身/指令确保处理器在执行锁定指令之前先执行所有指令。 /关于尚未刷新的写入/它强制在执行下一条指令之前将处理器内所有已发布的写入刷新到外部内存

/关于SMP/信号量在S状态的缓存中。。。发出0字节日期的读取和失效事务(这是对相邻CPU/中缓存线的共享副本的终止)


在过去的几天里,我一直在寻找实际数据,但什么也没找到。 然而,我做了一些研究,比较了原子操作的成本和缓存未命中的成本

在PentiumPro之前(如文档中所述),x86锁前缀(包括原子CA的
锁cmpxchg
)的成本是内存访问(如缓存未命中),+停止其他处理器的内存操作,+与试图锁定总线的其他处理器的争用。但是,由于奔腾处理器(PentiumPro)对于普通写回可缓存内存(应用程序处理的所有内存,除非您直接与硬件对话),因此不会阻止所有内存操作,只会阻止相关缓存线(基于中的链接)

i、 e.在实际的
锁定
ed操作的存储部分之前,核心延迟对线路的MESI共享和RFO请求的响应。这称为“缓存锁”,仅影响该缓存线。其他岩芯可同时装入/储存,甚至可装入其他管线


事实上,正如上面所解释的,CAS案例可能更加复杂,没有时间安排,而是由一位值得信赖的工程师进行了深刻的描述。(至少对于在实际CAS之前进行纯负载的正常用例。)

在深入讨论太多细节之前,我要说,锁定操作会导致一次缓存未命中+与同一缓存线上的其他处理器可能发生的争用,而CAS+之前的加载(除了互斥体上,几乎总是必需的,在互斥体上,总是CAS 0和1)会导致两次缓存未命中

他解释说,单个位置上的load+CAS实际上会导致两次缓存未命中,就像load-Linked/Store-Conditional(后者见此处)。他的解释依赖于对这个问题的了解。它对缓存线使用4种状态:M(odified)、E(exclusive)、S(hared)、I(nvalid)(因此称为MESI),下面将根据需要进行解释。已解释的场景如下所示:

  • 加载导致缓存未命中-相关缓存线在共享状态下从内存加载(即,仍允许其他处理器将该缓存线保留在内存中;在此状态下不允许更改)。如果该位置在内存中,则跳过此缓存未命中可能的成本:1次缓存未命中。(如果缓存线处于共享、独占或修改状态,即数据位于该CPU的一级缓存中,则跳过)
  • 程序计算要存储的新值
  • 它运行一个原子CAS指令。
    • 它必须避免并发修改,因此必须从其他CPU的缓存中删除缓存线的副本,才能将缓存线移动到独占状态可能成本:1次缓存未命中。如果缓存已为独占所有,即处于独占或修改状态,则不需要此项。在这两种状态下,没有其他CPU持有缓存线,但在独占状态下,缓存线尚未修改(尚未修改)
    • 在这种通信之后,变量在我们的CPU的本地缓存中被修改,此时它对所有其他CPU是全局可见的(因为它们的缓存与我们的一致)。它最终将根据通常的算法写入主存
    • 试图读取或修改该变量的其他处理器首先必须以共享或独占模式获取该缓存线,并为此联系该处理器并接收缓存线的更新版本。 相反,锁定操作只会导致缓存未命中(因为缓存线将在独占状态下直接请求)

在所有情况下,缓存线请求都可能被已经修改数据的其他处理器暂停。

我使用以下设置进行了一些分析:测试机器(AMD Athlon64 x2 3800+)已启动,切换到长模式(中断禁用),感兴趣的指令在循环中执行,展开100次迭代,循环1000次。循环体与16个字节对齐。在循环前后使用rdtsc指令测量时间。此外,执行一个没有任何指令的虚拟循环(每个循环迭代测量2个周期,其余循环测量14个周期),并从指令分析时间的结果中减去结果

测量了以下说明:

  • 锁定cmpxchg[rsp-8],rdx
    ”(具有比较匹配和不匹配)
  • lock xadd[rsp-8],rdx
  • 锁定基站qword ptr[rsp-8],1
在所有情况下,测量的时间约为310个周期,误差约为+/-8个周期

这是在相同(缓存)内存上重复执行的值。如果有额外的缓存未命中,则时间会高得多。同样,这是用2个co中的一个来完成的
#define LOOP_COUNT 1000
#define UNROLLED_COUNT 100

PUBLIC ProfileDummy
ProfileDummy:

    cli

    // Get current TSC value into r8
    rdtsc
    mov r8, rdx
    shl r8, 32
    or r8, rax

    mov rcx, LOOP_COUNT
    jmp looper1

.align 16
looper1:

REPEAT UNROLLED_COUNT
    // nothing, or add something to compare against
ENDR

    dec rcx
    jnz looper1

    // Put new TSC minus old TSC into rax
    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, r8

    ret

PUBLIC ProfileFunction
ProfileFunction:

    cli

    rdtsc
    mov r8, rdx
    shl r8, 32
    or r8, rax
    mov rcx, LOOP_COUNT

    jmp looper2

.align 16
looper2:

REPEAT UNROLLED_COUNT
    // Put here the code you want to profile
    // make sure it doesn't mess up non-volatiles or r8
    lock bts qword ptr [rsp - 8], 1
ENDR

    dec rcx
    jnz looper2

    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, r8

    ret