簡體   English   中英

為什么 memory_order_relaxed 在 x86 上使用原子(鎖定前綴)指令?

[英]Why does memory_order_relaxed use atomic (lock-prefixed) instructions on x86?

在 Visual C++ 2013 上,當我編譯以下代碼時

#include <atomic>

int main()
{
    std::atomic<int> v(2);
    return v.fetch_add(1, std::memory_order_relaxed);
}

我在 x86 上取回以下程序集:

51               push        ecx  
B8 02 00 00 00   mov         eax,2 
8D 0C 24         lea         ecx,[esp] 
87 01            xchg        eax,dword ptr [ecx] 
B8 01 00 00 00   mov         eax,1 
F0 0F C1 01      lock xadd   dword ptr [ecx],eax 
59               pop         ecx  
C3               ret              

同樣在 x64 上:

B8 02 00 00 00    mov         eax,2 
87 44 24 08       xchg        eax,dword ptr [rsp+8] 
B8 01 00 00 00    mov         eax,1 
F0 0F C1 44 24 08 lock xadd   dword ptr [rsp+8],eax 
C3                ret              

我只是不明白:為什么一個int變量的寬松增量需要一個lock前綴?

這是有原因的,還是他們根本不包括刪除它的優化?


* 我使用/O2/NoDefaultLib來修剪它並擺脫不必要的 C 運行時代碼,但這與問題無關。

因為它仍然需要一個鎖才能成為原子; 即使使用memory_order_relaxed ,遞增/遞減的要求也太嚴格而不能無鎖。

想象一下沒有鎖的情況。

v = 0;

然后我們生成 100 個線程,每個線程都使用以下命令:

v++;

然后你等待所有線程完成,你希望 v 是什么? 不幸的是,它可能不是 100。假設值 v=23 是由一個線程加載的,並且在創建 24 之前,另一個線程也加載了 23,然后也寫出了 24。 所以線程實際上相互否定。 這是因為增量本身不是原子的 當然,加載、存儲、添加本身可能是原子的,但遞增是多個步驟,因此它不是原子的。

但是對於 std::atomic,無論std::memory_order設置如何,所有操作都是原子的。 唯一的問題是它們將按什么順序發生。 memory_order_relaxed仍然保證原子性,它可能會因為它附近發生的任何其他事情而亂序,即使對相同的值進行操作。

原子操作,即使是寬松的排序,仍然必須原子的

即使當前 CPU 上的某些操作沒有lock前綴的原子操作(提示:它們不是,由於多核緩存),也不能保證未來的 CPU 可以使用。

僅僅因為您想從二進制文件中優化一個字節,依賴於不屬於程序集規范的一部分(因此不能保證在未來的 x86_64 中保留)架構)

當然,在這種情況下,多核系統很普遍,因此您實際上需要一個lock前綴才能在當前 CPU 上工作。 請參閱“int num”的 num++ 可以是原子的嗎?

首先,作為參考,考慮一個正常的分配。 它在 Intel/64 上生成以下內容:

// v = 10;
000000014000E0D0  mov         eax,0Ah  
000000014000E0D5  xchg        eax,dword ptr [v (014001BCDCh)]  

然后考慮一個輕松的任務:

// v.store(10, std::memory_order_relaxed);
000000014000E0D0  mov         dword ptr [v (014001BCDCh)],0Ah 

現在, std::atomic::fetch_add()是一個讀-修改-寫操作,以“骯臟”的方式執行此操作毫無意義。 默認情況下,您根據http://en.cppreference.com/w/cpp/atomic/atomic/fetch_add獲取std::memory_order_seq_cst 因此,我認為,為此生成單個本機指令是有意義的。 至少在便宜的 Intel/64 上:

// v.fetch_add(1, std::memory_order_relaxed)
000000014000E0D0  mov         eax,1  
000000014000E0D5  lock xadd   dword ptr [v (014001BCDCh)],eax  

畢竟,您可以通過顯式編寫編譯器必須遵守的兩個操作來實現您想要的:

// auto x = v.load(std::memory_order_relaxed);
000000014000E0D0  mov         eax,dword ptr [v (014001BCDCh)]  

// ++x;
000000014000E0D6  inc         eax  

//v.store(x, std::memory_order_relaxed);
000000014000E0D8  mov         dword ptr [v (014001BCDCh)],eax  

暫無
暫無

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

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