簡體   English   中英

Clang 不內聯 std::atomic::load 以加載 64 位結構

[英]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()

這對我來說是有問題的,原因有幾個:

  1. 更明顯的是,指令要多得多,所以我希望代碼效率較低
  2. 不太明顯,請注意生成的代碼還包括對庫函數__atomic_load的調用,這意味着我的二進制文件需要與 libatomic 鏈接。 這意味着我需要不同的庫列表來鏈接,具體取決於我的代碼用戶是使用 GCC 還是 Clang。
  3. 庫函數可能會使用鎖,這會降低性能

我現在想到的重要問題是是否有辦法讓 Clang 也將負載轉換為單個指令。 我們將它用作我們計划分發給其他人的庫的一部分,因此我們不能依賴正在使用的特定編譯器。 到目前為止向我建議的解決方案是使用類型雙關並將結構體與 64 位整數一起存儲在聯合中,因為 Clang 確實在一條指令中正確地以原子方式加載了 64 位整數。 然而,我對這個解決方案持懷疑態度,因為雖然它似乎適用於所有主要編譯器,但我已經讀到它實際上是未定義的行為。 如果其他人不熟悉該技巧,則此類代碼對其閱讀和理解也不是特別友好。

總而言之,有沒有辦法自動加載 64 位結構:

  1. 適用於 Clang 和 GCC,最好是大多數其他流行的編譯器,
  2. 編譯時生成一條指令,
  3. 不是未定義的行為,
  4. 讀者友好嗎?

這種鏗鏘的優化只發生在 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM