原子操作成本


90

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

缓存会受到什么影响?

我对流行的现代CPU感兴趣:x86,x86_64,PowerPC,SPARC,Itanium。


@Jason S,任何。cas和atomic inc / dec之间的差异可以忽略不计。
osgx

2
随着更多的争用放在内存地址上,x86上的原子操作会变慢。我相信它们通常比非锁定操作慢一个数量级,但是显然,这将取决于所使用的操作,争用和内存屏障。
Stephen Nutt 2010年

嗯。在x86上写似乎是原子的。“了解Linux内核”-> spin_unlock
osgx

32位写入在Java中是原子的,即,它是可移植的原子(但没有内存屏障语义,因此对于指针而言通常是不够的)。除非添加LOCK前缀,否则加1通常不是原子的。关于Linux内核,无需查看spin_unlock。请参阅当前版本中的arch / x86 / include / asm / atomic_32.h(以前是include / asm-i386 / atomic.h)。
Blaisorblade 2010年

@ Blaisorblade,JAva不在这里。锁定操作的成本是多少?
osgx

Answers:


59

我一直在寻找过去几天的实际数据,却一无所获。但是,我进行了一些研究,将原子操作的成本与缓存未命中的成本进行了比较。

lock cmpxchg在奔腾Pro(如文档中所述)之前,x86 LOCK前缀(包括用于原子CAS的代价)是内存访问(如高速缓存未命中),+停止其他处理器的内存操作,+与其他处理器的任何争用试图锁定公交车。但是,由于PentiumPro,对于普通的Writeback可缓存内存(除非您直接与硬件交谈,否则应用程序处理的所有内存)都不会阻塞所有内存操作,而是仅阻塞相关的缓存行(基于@osgx答案中的链接) 。

也就是说,核心会延迟应答线路的MESI份额和RFO请求,直到完成实际locked操作的存储部分之后。这称为“缓存锁”,仅影响该缓存行。其他内核可以同时加载/存储或什至CASing其他线路。


实际上,如本页所述,CAS案例可能会更加复杂,没有时机,而是由值得信赖的工程师进行的有见地的描述。(至少对于在实际CAS之前执行纯负载的正常用例而言。)

在详细介绍之前,我要说的是,LOCKed操作的代价是一次缓存未命中+与同一缓存行上的其他处理器可能发生的争用,而CAS +先前的负载(几乎总是需要的,除了互斥锁,您总是CAS 0和1)可能会导致两次高速缓存未命中。

他解释说,单个位置上的负载+ CAS实际上会造成两次高速缓存未命中,例如“负载链接/存储条件”(请参阅​​那里的后者)。他的解释依赖于MESI缓存一致性协议的知识。它为高速缓存行使用4个状态:M(已修改),E(已包含),S(已硬体),I(无效)(因此称为MESI),下面在需要时进行了说明。解释的场景如下:

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

在所有情况下,缓存行请求都可以由已经修改数据的其他处理器停止。


为什么更改其他cpus的状态成本会导致1次缓存未命中?
osgx 2010年

1
因为它是在CPU外部进行通信,因此比访问缓存要慢。无论如何,必须从其他CPU传递高速缓存未命中。实际上,如果使用最新的Xeon处理器上的直接互连(例如AMD Hypertransport(早在很久以前)或Intel的Intel QuickPath Interconnect),则与另一个CPU进行通信的速度可能比与存储器进行通信的速度更快。基于Nehalem。否则,与其他CPU的通信与用于内存的CPU在同一FSB上进行。在Wikipedia上搜索HyperTransport和Front Side Bus以获取更多信息。
Blaisorblade 2010年

哇,从来没有想过他这么贵-缓存未命中可能是数千个周期。
Lothar'7

2
真?我过去经常使用的数字是:缓存丢失的一百个周期,上下文/特权开关(包括syscalls)的数千个周期。
布莱布莱德(Blaisorblade)2011年

1
缓存未命中不是几千个周期!其大约100ns的,其通常为300-350 CPU周期....
user997112

37

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

测量了以下说明:

  • lock cmpxchg [rsp - 8], rdx”(比较匹配和不匹配),
  • lock xadd [rsp - 8], rdx”,
  • lock bts qword ptr [rsp - 8], 1

在所有情况下,测量的时间约为310个周期,误差约为+/- 8个周期

这是在相同(缓存)内存上重复执行的值。有了额外的高速缓存未命中,时间就会大大增加。而且,仅在活动的两个内核之一中完成此操作,因此缓存是专有拥有的,不需要缓存同步。

为了评估在高速缓存未命中时锁定指令的成本,我wbinvld在锁定指令之前添加了一条指令,并将wbinvld加号和add [rsp - 8], rax插入比较循环。在这两种情况下,每条指令对的成本约为80,000个周期!如果发生锁定,则每条指令的时间差约为180个周期。

请注意,这是互惠的吞吐量,但是由于锁定操作是序列化操作,因此延迟之间可能没有差异。

结论:锁定操作很繁重,但是缓存未命中可能会更重。另外:锁定的操作不会导致高速缓存未命中。当高速缓存行不是唯一拥有时,它只能引起高速缓存同步流量。

要启动机器,我使用了ReactOS项目中的x64版本的FreeLdr。这是asm源代码:

#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

谢谢!您可以发布测试代码还是自己测试Core2 / Core i3 / i5 / i7?您的测试设置中是否已初始化所有内核?
osgx

我添加了源代码。仅初始化了一个内核。很想看看其他机器的结果。
2013年

与整个缓存的WBINVD相比,CLFLUSH应该是一种更轻松的刷新缓存行的方法。 WBINVD也将刷新指令高速缓存,从而导致额外的高速缓存未命中。
彼得·科德斯

测试高速缓存行在“共享”状态下很热的情况可能很有趣。您可以通过让另一个线程以纯负载读取它来实现。
彼得·科德斯

4

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

Ppro和P2图书http://books.google.com/books?id=3gDmyIYvFH4C&pg=PA245&dq=lock+instruction+pentium&lr=&ei=_E61S5ehLI78zQSzrqwI&cd=1#v=onepage&q=lock%20instruction%20pentium&246=false页面244

锁定的指令正在序列化,同步操作../about乱序/锁定的RMW / read-modify-write =原子本身/指令可确保处理器在执行锁定的指令之前先执行所有指令。/ about尚未刷新的写入/会强制在执行下一条指令之前将处理器中所有已发布的写入刷新到外部存储器。

/ about SMP /信号量处于S状态的高速缓存中...针对0个字节的日期发出读取和无效事务(这是相邻CPU中高速缓存行的共享副本的销毁/)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.