切换atomic_bool的有效方法

Jos*_*ica 7 c x86 atomic compiler-optimization stdatomic

如果我有atomic_bool flag;,我如何编写 C 代码来切换它,使其原子、可移植且高效?关于“高效”,我希望它能够在 x86_64 上组装到lock xorb $1, flag(%rip). “显而易见”的东西flag = !flag;已经被淘汰了,因为它实际上不是原子的。我的下一个猜测是flag ^= true;,它在 GCC 上组装成一团糟:

        movzbl  flag(%rip), %eax
0:
        movb    %al, -1(%rsp)
        xorl    $1, %eax
        movl    %eax, %edx
        movzbl  -1(%rsp), %eax
        lock cmpxchgb   %dl, flag(%rip)
        jne     0b
Run Code Online (Sandbox Code Playgroud)

Clang 上的混乱:

        movb    flag(%rip), %al
0:
        andb    $1, %al
        movl    %eax, %ecx
        xorb    $1, %cl
        lock            cmpxchgb        %cl, flag(%rip)
        jne     0b
Run Code Online (Sandbox Code Playgroud)

然后我尝试通过这样做来指定较弱的内存顺序atomic_fetch_xor_explicit(&flag, true, memory_order_acq_rel);。这实现了我在 Clang 上想要的功能,但是 GCC 现在完全无法使用error: operand type '_Atomic atomic_bool *' {aka '_Atomic _Bool *'} is incompatible with argument 1 of '__atomic_fetch_xor'. 有趣的是,如果我的类型是 anatomic_char而不是 an atomic_bool,那么 GCC 和 Clang 都会发出我想要的程序集。有没有办法做我想做的事atomic_bool

Nat*_*dge 4

主要总结评论:

atomic_bool它看起来像是原子切换is的唯一便携式方法flag ^= 1。但正如您所指出的,gcc 和 clang 不知道如何优化它,并退回到 cmpxchg 循环。如果您想要完全的可移植性和合规性,我认为您只需要忍受这一点,直到他们修复错过的优化(您可能想要报告)。

原则上,另一个选项应该是flag -= 1flag += -1,当非零值被视为 true 时,它​​们具有相同的真值表。然而,gcc 将其编译为与 相同的低效 xor/cmpxchg 代码flag ^= 1,而 clang 实际上错误编译了它:when flag == 0, thenflag -= 1将设置flag0xff无效。看起来这个问题几年前就已经报道过,但仍然没有解决。

如果你想要一个解决方法,至少在 x86 上你应该能够做到

atomic_fetch_xor((atomic_uchar *)&flag, 1);
Run Code Online (Sandbox Code Playgroud)

认为严格别名是可以的,因为它atomic_uchar是一种字符类型。实际上,无论如何它很可能没问题,因为原子访问不应该被优化掉。为了安全起见,请检查生成的程序集,或者直接用适当的内联汇编替换整个程序集。


尽管 C 标准不支持它(7.17.7.5p1:“这些操作都不适用于 。”),但clang 扩展了atomic_fetch_*在 上工作的功能,这是一个很好的接触,我真的不明白为什么标准委员会包括该例外。所有这些操作仍然必须通过复合赋值运算符来实现,因此省略它们只会剥夺程序员使用任何弱内存排序的能力,而不会让实现变得更容易。atomic_boolatomic_boolatomic_boolatomic_fetch_*

出于类似的原因,我也不明白为什么他们没有提供atomic_fetch_*其余的复合赋值运算符。 atomic_fetch_mul可能没那么有用,但既然*=必须工作,它不应该给实现带来任何成本,而且一致性会很好。