簡體   English   中英

如何在缺少stdatomic.h的機器上使用原子整數?

[英]How to have atomic integers on machines that lack stdatomic.h?

我開發了一個多線程程序,它取決於stdatomic.h中atomic_int,atomic_store和atomic_load的可用性。 該計划由GCC編制。

現在,我試圖在幾個缺少stdatomic.h的舊操作系統版本上編譯程序失敗。 不幸的是,我需要能夠在舊機器上編譯程序。 因此,我在新的操作系統版本上編譯程序並在舊版本上運行二進制文件是不夠的。

有沒有辦法在舊機器上模擬stdatomic.h,也許有一些特定於GCC的內置函數?

雖然在舊的操作系統上安裝較新版本的GCC可能是解決方案,但是當前的構建系統已經硬編碼到其上的“gcc”,並且新的GCC必須從源代碼編譯,因為舊操作系統不應該在包管理系統中有它。 因此,理想情況下,答案將適用於舊的GCC版本。

雖然這不是一個完全適用於所有應用程序的解決方案,但我發現了一種支持所需基本功能的方法,並至少通過了一些基本的多線程測試:

#define _Atomic(T) struct { volatile __typeof__(T) __val; }

typedef _Atomic(int) atomic_int;

#define atomic_load(object) \
    __sync_fetch_and_add(&(object)->__val, 0)

#define atomic_store(object, desired) do { \
    __sync_synchronize(); \
   (object)->__val = (desired); \
    __sync_synchronize(); \
} while (0)

__sync_synchronize和__sync_fetch_and_add調用是必要的,否則線程之間的通信失敗(我沒有測試只刪除其中一個,我剛測試刪除它們)。

但是,我並不十分確信這種解決方案適用於所有情況。 我是從https://gist.github.com/nhatminhle/5181506找到的,作者不建議將它用於舊的GCC版本。

從理論上講,您也可以使用互斥鎖。 但是,互斥體的性能比原子性差。

編輯:

也可以通過以下方式實現atomic_store:

#define atomic_store(object, desired) do { \
    for (;;) \
    { \
        __typeof__((object)->__val) oldval = atomic_load(object); \
        if (__sync_bool_compare_and_swap(&(object)->__val, oldval, desired)) \
        { \
            break; \
        } \
    } \
} while (0)

然而,這始終如一地將性能從139280.5 ops /秒(標准差1799.6 ops /秒)降至131805.6 ops /秒(標准差986.03 ops /秒)。 因此,降低的性能具有統計意義。

編輯2:

循環方法具有以下匯編代碼:

.globl signal_completion
        .type   signal_completion, @function
signal_completion:
.LFB18:
        leaq    4(%rdi), %rcx
.L42:
        xorl    %eax, %eax
        lock
        xaddl   %eax, (%rcx)
        movl    $1, %edx
        movl    %eax, -4(%rsp)
        movl    -4(%rsp), %eax
        lock
        cmpxchgl        %edx, (%rcx)
        jne     .L42
        rep ; ret
.LFE18:
        .size   signal_completion, .-signal_completion
        .p2align 4,,15

而__sync_synchronize方法具有以下代碼:

.globl signal_completion
        .type   signal_completion, @function
signal_completion:
.LFB18:
        movl    $1, 4(%rdi)
        ret
.LFE18:
        .size   signal_completion, .-signal_completion
        .p2align 4,,15

...並且在具有stdatomic.h的機器上,它編譯為:

        .globl  signal_completion
        .type   signal_completion, @function
signal_completion:
.LFB43:
        .cfi_startproc
        movl    $1, 4(%rdi)
        mfence
        ret
        .cfi_endproc
.LFE43:
        .size   signal_completion, .-signal_completion

所以,我唯一真正缺乏的是mfence。 我猜它可以使用簡單的內聯匯編添加,例如:

asm volatile ("mfence" ::: "memory");

...放在atomic_store定義中的第二個__sync_synchronize()之后。

編輯3:

顯然,__sync_fetch_and_add沒有被優化掉,因為輪詢變量的循環具有此程序集輸出:

.L29:
        xorl    %eax, %eax
        lock
        xaddl   %eax, (%rdi)
        testl   %eax, %eax
        je      .L29

通過改為:

#define atomic_load(object) ((object)->__val)

你會得到:

.L29:
        movl    (%rdi), %eax
        testl   %eax, %eax
        je      .L29

這相當於stdatomic.h支持機器上的程序集:

.L38:
        movl    (%rdi), %eax
        testl   %eax, %eax
        je      .L38

奇怪的是,__sync_fetch_and_add變體似乎在我的機器和我的基准測試上運行得更快,即使它有更復雜的代碼。 奇怪的世界,不是嗎?

最好的事情是推出自己的包裝。 在可用時使用stdatomic,否則使用互斥鎖或平台特定指令模擬操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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