Dan*_*iel 4 c++ atomic clang atomicity stdatomic
考虑以下代码,它使用 astd::atomic原子地加载 64 位对象。
#include <atomic>
struct A {
int32_t x, y;
};
A f(std::atomic<A>& a) {
return a.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
使用 GCC,好事发生了,并生成了以下代码。( https://godbolt.org/z/zS53ZF )
f(std::atomic<A>&):
mov rax, QWORD PTR [rdi]
ret
Run Code Online (Sandbox Code Playgroud)
这正是我所期望的,因为我看不出为什么在这种情况下 64 位结构不能像任何其他 64 位字一样被对待。
但是,对于 Clang,情况就不同了。Clang 生成以下内容。( https://godbolt.org/z/d6uqrP )
f(std::atomic<A>&): # @f(std::atomic<A>&)
push rax
mov rsi, rdi
mov rdx, rsp
mov edi, 8
xor ecx, ecx
call __atomic_load
mov rax, qword ptr [rsp]
pop rcx
ret
mov rdi, rax
call __clang_call_terminate
__clang_call_terminate: # @__clang_call_terminate
push rax
call __cxa_begin_catch
call std::terminate()
Run Code Online (Sandbox Code Playgroud)
这对我来说是有问题的,原因有几个:
__atomic_load,这意味着我的二进制文件需要与 libatomic 链接。这意味着我需要不同的库列表来链接,具体取决于我的代码用户是使用 GCC 还是 Clang。我现在想到的重要问题是是否有办法让 Clang 也将负载转换为单个指令。我们将它用作我们计划分发给其他人的库的一部分,因此我们不能依赖正在使用的特定编译器。到目前为止向我建议的解决方案是使用类型双关并将结构体与 64 位 int 一起存储在联合中,因为 Clang 确实在一条指令中以原子方式正确加载了 64 位 int。然而,我对这个解决方案持怀疑态度,因为虽然它似乎适用于所有主要编译器,但我已经读到它实际上是未定义的行为。如果其他人不熟悉该技巧,则此类代码对其阅读和理解也不是特别友好。
总而言之,有没有办法自动加载 64 位结构:
这种铿锵的优化只发生在 libstdc++ 中;正如我们所期望的那样,在 Godbolt 内联上叮当响-stdlib=libc++。https://godbolt.org/z/Tt8XTX。
似乎给 struct 64 位对齐足以手持 clang。
libstdc++的std::atomic模板对自然对齐时足够小以成为原子的类型执行此操作,但也许 clang++atomic<T>在 libstdc++ 实现中只看到底层类型的对齐,而不是 的类成员。我没有调查过;有人应该将此报告给 clang / LLVM bugzilla。
#include <atomic>
#include <stdint.h> // you forgot this header.
struct A {
alignas(2 * sizeof(int32_t)) int32_t x;
int32_t y; // this one must be separate, otherwise y would also be aligned -> 16-byte object
};
A f(std::atomic<A>& a) {
return a.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
按结构大小对齐使其不可知alignof(int64_t),在 32 位 ABI 上可能只有 4。(并且我没有使用alignas(8)来避免在 char 为 32 位且 sizeof(int64_t) = 2 的系统上过度对齐。 ) 这可能是不必要的复杂,并且alignas(int64_t)更容易阅读,即使它并不总是与赋予此结构自然对齐相同的东西。)
# clang++ 9.0 -std=gnu++17 -O3; g++ is the same
f(std::atomic<A>&):
mov rax, qword ptr [rdi]
ret
Run Code Online (Sandbox Code Playgroud)
顺便说一句,不,libatomic库函数不会使用锁;它确实知道 8 字节对齐的加载自然是原子的,其他使用线程将使用普通加载/存储,而不是锁。
较旧的 clang 至少使用call __atomic_load_8而不是通用的可变大小的,但这仍然是一个很大的遗漏优化。
有趣的事实:clang -m32将用于lock cmpxchg8b实现 8 字节的原子加载,而不是使用 SSE 或fild像 GCC 那样。:/