g++/gcc 是否支持 C++20 新的 atomic_flag 特性?

Vor*_*ord 1 c++ gcc g++ stdatomic

根据cppreference,c++20 具有丰富的(对我来说很有用)的atomic_flag操作支持。

但是,尚不清楚 gcc 是否还支持这些功能,在gnu 的功能摘要中找不到它们。我目前正在使用带有-c++=2aset 的版本 8 。

这段代码不能用 GCC8 编译:

#include <atomic>

int main() {
  std::atomic_flag myFlag = ATOMIC_FLAG_INIT;
  myFlag.test();
}
Run Code Online (Sandbox Code Playgroud)

error: ‘struct std::atomic_flag’ has no member named ‘test’

我不想通过安装较新版本的 g++ 来破坏我的构建环境,并且感谢任何可以报告atomic_flag版本 10 或更高版本支持的人。

Pet*_*des 5

atomic<bool>做任何事情atomic_flag,就像在所有普通 C++ 实现上一样高效。C++20 刚刚向 atomic_flag 添加了新的东西,使其达到atomic<bool>. atomic_flag保证是lock_free,但在所有平台上有人关心的做法,所以是atomic<bool>

不要期望 GCC8 具有所有 C++2a 特性;至少在https://godbolt.org/上尝试使用最新版本或每晚 gcc。(另请注意,需要支持这一点的不是编译器本身,只是标准库头文件。但 libstdc++ 通常与 g++ 一起分发。)

我调整了您的示例,以便可以在启用优化的情况下编译它,而无需优化实际工作。

#include <atomic>

int flagtest(std::atomic_flag &myFlag) {
  //std::atomic_flag myFlag = ATOMIC_FLAG_INIT;
  return myFlag.test();
}
Run Code Online (Sandbox Code Playgroud)

带有 gcc 和 clang的 Godbolt 编译器资源管理器上:GCC10.2 不支持新的 C++20atomic_flag::test()成员函数,GCC nightly trunk build 支持。Clang 11.0 和主干可以,clang 10.0.1 不行。

# GCC trunk for x86-64 -O3 -std=gnu++2a
flagtest(std::atomic_flag&):
        movzx   eax, BYTE PTR [rdi]
        ret
booltest(std::atomic<bool>&):
        movzx   eax, BYTE PTR [rdi]
        test    al, al
        setne   al
        movzx   eax, al                # this is weird, GCC has gone insane.
        ret
Run Code Online (Sandbox Code Playgroud)

使用 clang,我们还可以尝试 libc++(C++ 标准库的新实现)。默认情况下,Linux(包括 Godbolt)上的 clang 使用 libstdc++,就像 GCC 一样。

# clang 11.0 -O3 -std=gnu++2a -stdlib=libc++
flagtest(std::__1::atomic_flag&):      # @flagtest(std::__1::atomic_flag&)
        mov     al, byte ptr [rdi]
        movzx   eax, al
        and     eax, 1
        ret
booltest(std::__1::atomic<bool>&):         # @booltest(std::__1::atomic<bool>&)
        mov     al, byte ptr [rdi]
        movzx   eax, al
        and     eax, 1
        ret
Run Code Online (Sandbox Code Playgroud)

这太奇怪了,太可怕了;即使内存中的值可能没有被布尔化,也没有理由不合并到 RAX 的低字节,然后 movzx eax,al,而不是首先进行 movzx 加载。

但是and eax,1,如果 GCC 认为需要重新布尔化,那么它比 GCC 的疯狂 test/setnz/movzx 更糟糕。(它实际上并不需要这样做;ABI 保证内存中的 bool 是实际的01字节,并atomic<bool>使用与 相同的对象表示bool。)

因此,使用 clang,两种方式都会将愚蠢的错过优化转换为int. 由于某种原因atomic_flag,使用 GCC不会遇到这个问题,但我不建议仅仅因为这个原因使用它。希望atomic<bool>会得到修复,通常您不会将 bool 转换为 int。


正常使用atomic<bool>or atomic_flag,就像在它上面分支​​一样,不应该有任何这些遗漏的优化。 例如

int g0, g1;
int conditional_load(std::atomic<bool> &myFlag) {
    return myFlag ? g0 : g1;
}
Run Code Online (Sandbox Code Playgroud)
# gcc 11 nightly build -O3
conditional_load(std::atomic<bool>&):
        movzx   eax, BYTE PTR [rdi]
        test    al, al
        mov     eax, DWORD PTR g0[rip]
        cmove   eax, DWORD PTR g1[rip]
        ret
Run Code Online (Sandbox Code Playgroud)

所以这很正常。Clang 选择在地址之间进行选择,然后加载一次。这将负载使用延迟放在关键路径上并需要更多指令;当两个变量相邻时更糟糕的选择,所以可能来自同一个缓存行。(GCC 的选择总是涉及两个变量,如果可以在缓存中保持“冷”状态可能会更糟)。