原子操作(比较和交换或原子加/减的任何操作)的成本是多少?它消耗多少个周期?它会暂停SMP或NUMA上的其他处理器,还是会阻止内存访问?它将刷新乱序CPU中的重排序缓冲区吗?
缓存会受到什么影响?
我对流行的现代CPU感兴趣:x86,x86_64,PowerPC,SPARC,Itanium。
原子操作(比较和交换或原子加/减的任何操作)的成本是多少?它消耗多少个周期?它会暂停SMP或NUMA上的其他处理器,还是会阻止内存访问?它将刷新乱序CPU中的重排序缓冲区吗?
缓存会受到什么影响?
我对流行的现代CPU感兴趣:x86,x86_64,PowerPC,SPARC,Itanium。
Answers:
我一直在寻找过去几天的实际数据,却一无所获。但是,我进行了一些研究,将原子操作的成本与缓存未命中的成本进行了比较。
lock cmpxchg
在奔腾Pro(如文档中所述)之前,x86 LOCK前缀(包括用于原子CAS的代价)是内存访问(如高速缓存未命中),+停止其他处理器的内存操作,+与其他处理器的任何争用试图锁定公交车。但是,由于PentiumPro,对于普通的Writeback可缓存内存(除非您直接与硬件交谈,否则应用程序处理的所有内存)都不会阻塞所有内存操作,而是仅阻塞相关的缓存行(基于@osgx答案中的链接) 。
也就是说,核心会延迟应答线路的MESI份额和RFO请求,直到完成实际lock
ed操作的存储部分之后。这称为“缓存锁”,仅影响该缓存行。其他内核可以同时加载/存储或什至CASing其他线路。
实际上,如本页所述,CAS案例可能会更加复杂,没有时机,而是由值得信赖的工程师进行的有见地的描述。(至少对于在实际CAS之前执行纯负载的正常用例而言。)
在详细介绍之前,我要说的是,LOCKed操作的代价是一次缓存未命中+与同一缓存行上的其他处理器可能发生的争用,而CAS +先前的负载(几乎总是需要的,除了互斥锁,您总是CAS 0和1)可能会导致两次高速缓存未命中。
他解释说,单个位置上的负载+ CAS实际上会造成两次高速缓存未命中,例如“负载链接/存储条件”(请参阅那里的后者)。他的解释依赖于MESI缓存一致性协议的知识。它为高速缓存行使用4个状态:M(已修改),E(已包含),S(已硬体),I(无效)(因此称为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个周期!如果发生锁定,则每条指令的时间差约为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 /设备使用它。
锁定的指令正在序列化,同步操作../about乱序/锁定的RMW / read-modify-write =原子本身/指令可确保处理器在执行锁定的指令之前先执行所有指令。/ about尚未刷新的写入/会强制在执行下一条指令之前将处理器中所有已发布的写入刷新到外部存储器。
/ about SMP /信号量处于S状态的高速缓存中...针对0个字节的日期发出读取和无效事务(这是相邻CPU中高速缓存行的共享副本的销毁/)