使用较旧的gcc __sync builtins进行原子获取/加载和设置/存储

时间:2014-09-03 15:02:43

标签: c gcc

我正在使用gcc 4.6的(供应商分支机构),并且需要对无符号整数进行4次基本原子操作

  • 原子增量
  • 原子减量
  • Atomic set
  • Atomic get

。此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。

1 个答案:

答案 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),您需要MOVMFENCE后跟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原子将一般可用,因此可以解决解决可移植性问题。