簡體   English   中英

x86 架構的內存排序限制

[英]Memory ordering restrictions on x86 architecture

Anthony Williams 在他的偉大著作《C++ 並發實踐》中寫道(第 309 頁):

例如,在 x86 和 x86-64 架構上,原子加載操作總是相同的,無論是標記 memory_order_relaxed 還是 memory_order_seq_cst(參見第 5.3.3 節)。 這意味着使用寬松的內存排序編寫的代碼可能適用於具有 x86 架構的系統,而在具有更細粒度的內存排序指令集(例如 SPARC)的系統上可能會失敗。

在 x86 架構上,所有原子加載操作都是memory_order_seq_cst嗎? 此外,在cppreference std::memory_order站點上提到在 x86 上發布獲取排序是自動的。

如果此限制有效,排序是否仍適用於編譯器優化?

是的,排序仍然適用於編譯器優化。

此外,在 x86 上“原子加載操作始終相同”並不完全准確。

在 x86 上,使用mov完成的所有加載都具有獲取語義,並且使用mov完成的所有存儲都具有釋放語義。 所以acq_rel, acq 和relaxed load 是簡單的mov s,類似acq_rel, rel 和relaxed store(acq store 和rel load 總是等於relaxed)。

然而,這不是seq_cst不一定是真的:建築保證seq_cst語義mov 事實上,x86 指令集並沒有任何特定的指令來實現順序一致的加載和存儲。 只有 x86 上的原子讀-修改-寫操作才會有 seq_cst 語義。 因此,您可以通過執行參數為 0 的 fetch_and_add 操作( lock xadd指令)來獲取加載的 seq_cst 語義,並通過執行 seq_cst 交換操作( xchg指令)並丟棄之前的值來獲取存儲的 seq_cst 語義。

但是你不需要兩者都做! 只要所有 seq_cst 存儲都使用xchg完成,seq_cst 加載可以簡單地使用mov 雙重地,如果所有加載都使用lock xadd完成, lock xadd seq_cst 存儲可以簡單地使用mov

xchglock xaddmov慢得多。 因為程序(通常)的加載比存儲多,所以用xchg進行 seq_cst 存儲很方便,這樣(更頻繁的) seq_cst 加載可以簡單地使用mov 此實現細節已編入 x86 應用程序二進制接口 (ABI) 中。 在 x86 上,兼容編譯器必須將 seq_cst 存儲編譯為xchg以便可以使用更快的mov指令完成 seq_cst 加載(可能出現在另一個翻譯單元中,用不同的編譯器編譯)。

因此,在 x86 上使用相同的指令完成 seq_cst 和獲取加載通常是不正確的。 之所以如此,是因為 ABI 指定將 seq_cst 存儲編譯為xchg

編譯器當然必須遵循語言的規則,無論它運行在什么硬件上。

他說的是,在 x86 上,您沒有寬松的順序,因此即使您不要求,您也會得到更嚴格的順序。 這也意味着,在x86處理器上測試這樣的代碼可能不是確實有松散排序的系統上正常工作。

值得記住的是,盡管負載松弛和 seq_cst 負載可能映射到 x86 上的相同指令,但它們並不相同。 加載松弛可以由編譯器自由地跨內存操作重新排序到不同的內存位置,而 seq_cst 加載不能跨其他內存操作重新排序。

書中的句子寫得有點誤導。 在架構上獲得的排序不僅取決於您如何轉換原子負載,還取決於您如何轉換原子存儲。

在 x86 上實現seq_cst的常用方法是在任何seq_cst存儲和來自同一線程的后續seq_cst加載之間的某個點刷新存儲緩沖區。 編譯器保證這一點的常用方法是在存儲之后刷新,因為存儲少於加載。 在這個翻譯中, seq_cst加載不需要刷新。

如果您只使用簡單的加載和存儲對 x86 進行編程,則保證加載提供acquire語義,而不是seq_cst

至於編譯器優化,在 C11/C++11 中,編譯器根據特定原子的語義根據代碼移動進行優化,然后再考慮底層硬件。 (硬件可能會提供更強的排序,但編譯器沒有理由因此限制其優化。)

在 x86 架構上,所有原子加載操作都是memory_order_seq_cst嗎?

只有(程序的,程序中某些線程間可見操作的)執行才能是順序的。 單個操作本身不是順序的。

詢問單個隔離操作的實現是否是順序的是一個毫無意義的問題。

需要某種保證的所有內存操作的轉換必須遵循啟用該保證的策略。 可能有不同的策略具有不同的編譯器復雜性成本和運行時成本。

[只是有不同的策略來實現虛函數:唯一可以的(符合我們對速度、可預測性和簡單性的所有期望)是使用 vtables,所以所有編譯器都使用 vtable,但沒有定義虛函數作為通過 vtable。]

實際上,在給定的 CPU(我知道)上實現memory_order_seq_cst操作的策略並沒有很大不同。 編譯器之間的差異很小,不會妨礙二進制兼容性。 但存在潛在差異,多線程程序的高級全局優化可能為更高效的原子操作代碼生成開辟新的機會。

根據您的編譯器,僅包含std::atomic<>對象的寬松加載和memory_order_seq_cst修改的程序可能僅表現出順序行為,也可能不表現出順序行為,即使在強有序 CPU 上也是如此。

暫無
暫無

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

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