原子操作的成本是多少(比较和交换或原子加/减)?它消耗多少周期?它会暂停 SMP 或 NUMA 上的其他处理器,还是会阻止内存访问?它会在乱序 CPU 中刷新重新排序缓冲区吗?
对缓存会有什么影响?
我对现代流行的 CPU 很感兴趣:x86、x86_64、PowerPC、SPARC、Itanium。
原子操作的成本是多少(比较和交换或原子加/减)?它消耗多少周期?它会暂停 SMP 或 NUMA 上的其他处理器,还是会阻止内存访问?它会在乱序 CPU 中刷新重新排序缓冲区吗?
对缓存会有什么影响?
我对现代流行的 CPU 很感兴趣:x86、x86_64、PowerPC、SPARC、Itanium。
这几天我一直在寻找实际数据,但一无所获。但是,我做了一些研究,比较了原子操作的成本和缓存未命中的成本。
lock cmpxchg
在 PentiumPro 之前(如文档中所述)的 x86 LOCK 前缀(包括原子 CAS)的成本是内存访问(如缓存未命中),+ 停止其他处理器的内存操作,+ 与其他处理器的任何争用试图锁定总线。但是,由于 PentiumPro,对于普通的 Writeback 可缓存内存(应用程序处理的所有内存,除非您直接与硬件交谈),而不是阻止所有内存操作,而是仅阻止相关的缓存行(基于@osgx 答案中的链接) .
即核心延迟对线路的MESI 共享和RFO 请求的响应,直到实际lock
ed 操作的存储部分之后。这称为“高速缓存锁”,并且只影响那一个高速缓存行。其他核心可以同时加载/存储甚至 CASing 其他线路。
实际上,CAS 案例可能更复杂,如本页所述,没有时间安排,但值得信赖的工程师有深刻的描述。(至少对于在实际 CAS 之前进行纯加载的正常用例。)
在讨论太多细节之前,我会说一个 LOCKed 操作会花费一次缓存未命中 + 可能与同一缓存行上的其他处理器争用,而 CAS + 前面的负载(这几乎总是需要的,除了互斥锁,你总是CAS 0 和 1) 可能会导致两次缓存未命中。
他解释说,单个位置上的加载 + CAS 实际上会导致两次缓存未命中,例如 Load-Linked/Store-Conditional(后者见那里)。他的解释依赖于MESI 缓存一致性协议的知识。它对缓存线使用 4 种状态:M(odified)、E(xclusive)、S(hared)、I(nvalid)(因此称为 MESI),下面将在需要时进行解释。解释的场景如下:
在所有情况下,缓存线请求都可能被其他已经修改数据的处理器停止。
我使用以下设置进行了一些分析:启动了测试机器(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 个周期!在锁定 bts 的情况下,时间差约为每条指令 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
在基于总线的 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&f=false第 244-246 页
锁定指令是序列化、同步操作.... /about Out-of-order/locked RMW/read-modify-write = atomic itself/指令确保处理器在执行锁定指令之前执行所有指令。/关于尚未刷新的写入/它强制在执行下一条指令之前将处理器内的所有已发布写入刷新到外部存储器。
/about SMP/ 信号量在 S 状态的缓存中...针对 0 字节日期发出读取和无效事务(这是相邻 CPU 中缓存行的共享副本的终止/)