【问题标题】:atomic get/load and set/store with older gcc __sync builtins使用较旧的 gcc __sync 内置函数进行原子获取/加载和设置/存储
【发布时间】:2014-10-28 03:42:09
【问题描述】:

我正在使用 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。

【问题讨论】:

  • 您可能正在编写一些汇编语言。您的选择完全不同,具体取决于这是哪种 CPU 架构。请说明。
  • 你也可以使用像 ConcurrencyKit 这样相对轻量级的库:concurrencykit.org/doc/ck_pr_store.html
  • 你有__sync_lock_test_and_set吗?

标签: c gcc


【解决方案1】:

__sync_xxx() 以一些 Intel 原语为模型,在您的 x86 上,原子加载/存储非常简单,我认为这就是该集合看起来不完整的原因。

对于原子存储,我认为你被__sync_val_compare_and_swap() 卡住了,尽管像__sync_fetch_and_add() 一样加载,这显然是矫枉过正:-(

有“完整的内存屏障”__sync_synchronize(),但我无法发现它的作用(除了通过实验,在 x86_64 上)!如果您确切知道要为哪些机器进行编译,那么您可能会很开心地吸吮和观察……从包装在 __sync_synchronize() 中的加载和存储开始。

我可以告诉你,对于 x86 和 x86_64,原子加载不需要任何额外的——普通读取就可以了。如果您想要memory_order_seq_cst,原子商店需要mfence,否则不需要。但是...__sync_xxx 系列中缺少的另一件事是 编译器 障碍...除非那是 __sync_synchronize() 实际所做的!


稍后添加...

我推荐 C/C++11 mappings to processors 以很好地描述如何在 x86/x86_64、ARM 和 PowerPC 上实现原子。

使用__sync_val_compare_and_swap() 作为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) ;
  } ;

在您的 x86/x86_64 上,对于 memory_order_seq_cst (SC),您需要 LOCK XCHGMOV 后跟 MFENCE...所以在循环中使用 LOCK CMPXCHG 有点痛苦。对于 ARM 来说,这也有点痛苦,但更痛苦:-(


手动滚动原子加载/存储仅限于勇敢者(或鲁莽者)......并且,取决于 __sync_synchronize() 在给定机器上实际执行的操作,可能会或可能不会工作

所以,简单的方法是:

   __sync_synchronize() ;
   v = v_atomic ;           // atomic load !
   __sync_synchronize() ;

   __sync_synchronize() ;
   v_atomic = v ;           // atomic store !
   __sync_synchronize() ;

其中,对于 x86/x86_64 编译(对我来说,在 gcc 4.8 for x86_64 上):

    mfence
    mov    xxx, xxx
    mfence

用于加载和存储。这绝对是安全的(和 SC)...对于加载它可能会或可能不会比 LOCK XADD 更好...对于存储它可能比 LOCK CMPXCHG 和它周围的循环更好!

如果(且仅当)对于 ARM 编译为:

    dmb
    ldr/str
    dmb

那么 to 是安全的(和 SC)。

现在...对于 处理器 的 x86/x86_64,您不需要任何 MFENCE 来加载,甚至对于 SC。但是您确实需要阻止 编译器 重新排序内容。 __sync_synchronize() 和种植 mfence 一样。对于 gcc,您可以使用以下 voodoo 构造一个 __sync_compiler()

  #define __sync_compiler() __asm__ __volatile__("":::"memory")

我深信__sync_synchronize()(对于x86/x86_64)是有效的:

  #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() ;

并且...如果您可以使用 memory_order_release,那么您可以将唯一剩余的 _sync_synchronize() 替换为 _sync_compiler()

现在,对于 ARMv7... 如果(且仅当 -- 我没有 ARM,因此无法测试)__sync_synchronize() 编译为 dmb,那么我们可以这样做负载稍微好一点:

    __sync_compiler() ;
    v = v_atomic ;            // atomic load
    __sync_synchronize() ;

对于所有内存订单:memory_order_seq_cst 和 _acquire(和 _consume)。

对于 memory_order_release 我们可以:

    __sync_synchronize() ;
    v_atomic = v ;            // atomic store -- memory_order_release
    __sync_compiler() ;

对于 ARMv8,似乎有特殊的 LDASTL 指令......但我在这里有点超出我的深度。

注意:这是我相信的C/C++11 mappings to processors,但不能证明 ARM 的真实性。

无论如何...如果您准备手动执行原子加载/存储,那么您可以做得更好。

所以...如果这些东西的速度真的很重要,我会很想手动滚动,假设目标架构数量有限,并注意到:

  • 无论如何你都在使用 gcc 特定的东西,所以__sync_compiler() 技巧不会引入额外的可移植性问题。

  • __sync_xxx 系列已被 gcc 中更完整的 __atomic_xxx 所取代,因此如果您将来需要添加另一个目标架构,那么您也许可以升级到 __atomic_xxx .

  • 而且,在不远的将来,标准 C11 原子将普遍可用,因此可以解决可移植性问题。

【讨论】:

  • __sync_synchronize() 是一个内存屏障(例如,产生一个 mfense 指令),并且 __sync_xxx 文档似乎暗示它们也是一个内存屏障。我将如何使用 __sync_val_compare_and_swap 来实现商店?
  • 你的a_store函数无效; do 语句不完整
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-29
  • 2019-06-07
  • 1970-01-01
  • 2016-05-23
  • 1970-01-01
相关资源
最近更新 更多