[英]Clang doesn't inline std::atomic::load for loading 64-bit structs
考慮以下代碼,它使用std::atomic
以原子方式加載 64 位對象。
#include <atomic>
struct A {
int32_t x, y;
};
A f(std::atomic<A>& a) {
return a.load(std::memory_order_relaxed);
}
使用 GCC,好事發生了,並生成了以下代碼。 ( https://godbolt.org/z/zS53ZF )
f(std::atomic<A>&):
mov rax, QWORD PTR [rdi]
ret
這正是我所期望的,因為我看不出為什么在這種情況下 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()
這對我來說是有問題的,原因有幾個:
__atomic_load
的調用,這意味着我的二進制文件需要與 libatomic 鏈接。 這意味着我需要不同的庫列表來鏈接,具體取決於我的代碼用戶是使用 GCC 還是 Clang。我現在想到的重要問題是是否有辦法讓 Clang 也將負載轉換為單個指令。 我們將它用作我們計划分發給其他人的庫的一部分,因此我們不能依賴正在使用的特定編譯器。 到目前為止向我建議的解決方案是使用類型雙關並將結構體與 64 位整數一起存儲在聯合中,因為 Clang 確實在一條指令中正確地以原子方式加載了 64 位整數。 然而,我對這個解決方案持懷疑態度,因為雖然它似乎適用於所有主要編譯器,但我已經讀到它實際上是未定義的行為。 如果其他人不熟悉該技巧,則此類代碼對其閱讀和理解也不是特別友好。
總而言之,有沒有辦法自動加載 64 位結構:
這種鏗鏘的優化只發生在 libstdc++ 中; 正如我們對-stdlib=libc++
所期望的那樣,在 Godbolt 內聯上叮當-stdlib=libc++
。 https://godbolt.org/z/Tt8XTX 。
似乎給 struct 64 位對齊足以手持 clang。
libstdc++
的std::atomic
模板對自然對齊時足夠小以成為原子的類型執行此操作,但也許 clang++ 在 libstdc++ 實現中只看到底層類型的對齊,而不是atomic<T>
的類成員. 我沒有調查過; 有人應該將此報告給 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);
}
按結構大小對齊使其與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
順便說一句,不, libatomic
庫函數不會使用鎖; 它確實知道 8 字節對齊的加載自然是原子的,其他使用線程將使用普通加載/存儲,而不是鎖。
較舊的 clang 至少使用call __atomic_load_8
而不是通用的可變大小的call __atomic_load_8
,但這仍然是一個很大的遺漏優化。
有趣的事實: clang -m32
將使用lock cmpxchg8b
來實現 8 字節的原子加載,而不是像 GCC 那樣使用 SSE 或fild
。 :/
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.