我正在使用gcc 4.6的(供应商分支机构),并且需要对无符号整数进行4次基本原子操作
。此gcc版本不支持较新的__atomix_XXX内置,只有__sync内置。
这意味着我可以执行以下操作:
#define ATOMIC_INC(ptr) __sync_fetch_and_add((ptr),1)
#define ATOMIC_DEC(ptr) __sync_fetch_and_sub((ptr),1)
#define ATOMIC_GET(ptr) __sync_fetch_and_add((ptr),0)
但是,我找不到实现#define ATOMIC_SET()
的方法,它会自动设置变量,有没有办法用gcc 4.6.x实现这个目的?
另外,有没有更好的方法来实现上述ATOMIC_GET()
?从原子的角度来看,生成的程序集确实看起来很好,尽管由于实际执行了添加操作,它是次优的。
编辑:有问题的架构是ARMv6,x86和x86_64。
答案 0 :(得分:6)
__sync_xxx()
是以某些英特尔原语为模型的,在你的x86上,原子加载/存储非常简单,我认为这个集合看起来不完整。
对于原子商店,我认为你被__sync_val_compare_and_swap()
困住了,虽然像加载__sync_fetch_and_add()
一样,但显然有点过分了: - (
有完整的记忆障碍" __sync_synchronize()
,但我还没有发现它的作用(除了通过实验,在x86_64上)!如果你确切地知道你正在编译什么样的机器,那么你可以玩得很开心......从__sync_synchronize()
开始加载和存储开始。
我可以告诉你,对于x86和x86_64,原子加载不需要任何额外的普通读取。如果您需要mfence
,原子商店需要memory_order_seq_cst
,否则不需要__sync_xxx
。但是...... __sync_synchronize()
系列中缺少的另一件事是编译器屏障......除非__sync_val_compare_and_swap()
实际上做了什么!!
稍后添加......
我建议C/C++11 mappings to processors详细说明如何在x86 / x86_64,ARM和PowerPC上实现原子。
使用int
作为 void a_store(int* p_ai, int val)
{
int ai_was ;
ai_was = *p_ai ;
do { ai_was = __sync_val_compare_and_swap (p_ai, ai_was, val) ;
} ;
的原子商店:
LOCK XCHG
在x86 / x86_64上,对于memory_order_seq_cst(SC),您需要MOV
或MFENCE
后跟LOCK CMPXCHG
...所以使用 __sync_synchronize() ;
v = v_atomic ; // atomic load !
__sync_synchronize() ;
__sync_synchronize() ;
v_atomic = v ; // atomic store !
__sync_synchronize() ;
一个循环有点痛苦。对于ARM来说,这也有点痛苦,但更是如此: - (
手动滚动原子加载/存储严格来说是勇敢的(或者是蛮干的)......并且,根据__sync_synchronize()在给定计算机上实际执行的操作,可能会也可能不起作用!
所以,琐碎的方法是:
mfence
mov xxx, xxx
mfence
其中,对于x86 / x86_64编译(对我来说,在gcc 4.8对于x86_64)编译为:
LOCK XADD
用于加载和存储。这绝对是安全的(和SC)...对于加载它可能会或可能不会比LOCK CMPXCHG
更好......对于商店来说它可能比 dmb
ldr/str
dmb
更好并且围绕它循环!
如果(且仅当)用于ARM,则编译为:
MFENCE
然后那是安全的(和SC)。
现在......对于处理器的x86 / x86_64,您根本不需要__sync_synchronize()
来加载,甚至SC也不需要。但是你需要阻止编译器重新排序。 mfence
执行此操作以及种植__sync_compiler()
。对于gcc,您可以使用以下voodoo构建 #define __sync_compiler() __asm__ __volatile__("":::"memory")
:
__sync_synchronize()
我深情地相信 #define __sync_mfence() __asm__ __volatile__("mfence":::"memory")
(对于x86 / x86_64)是有效的:
__sync_compiler() ;
v = v_atomic ; // atomic load -- memory_order_seq_cst
__sync_compiler() ;
__sync_compiler() ;
v_atomic = v ; // atomic store -- memory_order_seq_cst
__sync_synchronize() ;
因为x86 / x86_64的表现非常好,所以你可以:
_sync_synchronize()
AND ...如果你可以使用memory_order_release,那么你可以用_sync_compiler()
替换剩下的唯一__sync_synchronize()
!
现在,对于ARMv7 ... if(且仅当 - 我没有ARM,因此无法测试它)dmb
编译为 __sync_compiler() ;
v = v_atomic ; // atomic load
__sync_synchronize() ;
,那么我们可以做得更好一点:
__sync_synchronize() ;
v_atomic = v ; // atomic store -- memory_order_release
__sync_compiler() ;
所有内存订单:memory_order_seq_cst和_acquire(和_consume)。
对于memory_order_release,我们可以:
LDA
对于ARMv8,似乎有特殊的STL
和__sync_compiler()
指令......但我在这里有点偏离我的深度。
注意:这是我信赖的C/C++11 mappings to processors,但不能证明ARM的真相。
无论如何......如果您准备手动滚动原子载荷/商店,那么您可以做得更好。
所以......如果这些东西的速度非常重要,我会倾向于手动滚动,假设目标架构数量有限,并注意到:
无论如何你都在使用gcc特定的东西,所以__sync_xxx
技巧不会引入额外的可移植性问题。
__atomic_xxx
系列已被gcc中更完整的__atomic_xxx
取代,因此如果您将来需要添加其他目标体系结构,那么您可以升级到{{1}}。
并且,在不太遥远的未来,标准的C11原子将一般可用,因此可以解决解决可移植性问题。