簡體   English   中英

fetch_add(0, memory_order_relaxed/release) 到 mfence + mov 的轉換是否合法?

[英]Is the transformation of fetch_add(0, memory_order_relaxed/release) to mfence + mov legal?

論文N4455 No Sane Compiler Will Optimize Atomics討論了編譯器可以應用於原子的各種優化。 在關於Atomics 的優化部分,對於 seqlock 示例,它提到了在 LLVM 中實現的轉換,其中fetch_add(0, std::memory_order_release)變成了mfence然后是普通加載,而不是通常的lock addxadd . 這個想法是這樣避免了對緩存行的獨占訪問,並且相對便宜。 無論提供的排序約束如何,都仍然需要mfence ,以防止對生成的mov指令進行StoreLoad重新排序。

無論順序如何,都會為此類read-don't-modify-write操作執行此轉換,並且為fetch_add(0, memory_order_relaxed).生成等效程序集fetch_add(0, memory_order_relaxed).

但是,我想知道這是否合法。 C++ 標准在[atomic.order]下明確指出:

原子讀-修改-寫操作應始終讀取在與讀-修改-寫操作關聯的寫之前寫入的最后一個值(按修改順序)。

安東尼威廉姆斯之前也注意到了有關 RMW 運營看到“最新”價值的事實。

我的問題是:基於原子變量的修改順序,基於編譯器是否發出lock add vs mfence然后是普通加載,線程可以看到的值的行為是否存在差異? 這種轉換是否有可能導致執行 RMW 操作的線程加載比最新值更舊的值? 這是否違反了 C++ 內存模型的保證?

(我不久前開始寫這個,但停滯不前;我不確定它加起來是一個完整的答案,但我認為其中一些可能值得發布。我認為@LWimsey 的評論更能觸及核心答案比我寫的要好。)

是的,它很安全。

請記住,as-if 規則的適用方式是,在真實機器上的執行必須始終產生與 C++ 抽象機器上的一種可能執行相匹配的結果。 優化使某些 C++ 抽象機允許在目標上不可能的執行是合法的。 即使完全針對 x86 進行編譯也會使所有 IRIW 重新排序成為不可能,例如,無論編譯器是否喜歡它。 (見下文;一些 PowerPC 硬件是唯一可以在實踐中做到這一點的主流硬件。)


我認為專門針對 RMW 的措辭的原因是它將負載與 ISO C++ 要求為每個原子對象分別存在的“修改順序”聯系起來。 (也許。)

請記住,C++ 正式定義其排序模型的方式是同步與每個對象的修改順序的存在(所有線程都可以達成一致)。 不像其中存在相干高速緩存1的概念創建的存儲器中的單個連貫的視圖,每個內核訪問硬件。 連貫共享內存的存在(通常使用 MESI 始終保持連貫性)使許多事情變得隱含,例如無法讀取“陳舊”值。 (盡管硬件內存模型通常像 C++ 一樣明確記錄它)。

因此轉換是安全的。

ISO C++ 在另一部分的注釋中確實提到了一致性的概念: http : //eel.is/c++draft/intro.races#14

由評估 B 確定的原子對象 M 的值應是由修改 M 的某個副作用 A 存儲的值,其中 B 不在 A 之前發生。
[注 14:這種副作用的集合也受到這里描述的其他規則的限制,特別是下面的連貫性要求。 — 尾注]

...

[注 19:前面的四個一致性要求有效地禁止編譯器將原子操作重新排序為單個對象,即使這兩個操作都是寬松加載。 這有效地使大多數硬件提供的緩存一致性保證可用於 C++ 原子操作。 — 尾注]

[注20:原子負載觀察到的值取決於“發生之前”關系,這取決於原子負載觀察到的值。 The intended reading is that there must exist an association of atomic loads with modifications they observe that, together with suitably chosen modification orders and the “happens before” relation derived as described above, satisfy the resulting constraints as imposed here. — 尾注]

所以 ISO C++ 本身注意到緩存一致性給出了一些排序,而 x86 有一致性緩存。 (我沒有完整地論證這是足夠的排序,抱歉。LWimsey 關於修改順序中最新意味着什么的評論是相關的。)

(在許多 ISA(但不是全部)上,當您將存儲存儲到 2 個單獨的對象時,內存模型還排除了IRIW 重新排序。(例如,在 PowerPC 上,2 個讀取器線程可能會不同意 2 個存儲到 2 個單獨對象的順序)。非常很少有實現可以創建這樣的重新排序:如果共享緩存是數據可以在內核之間獲取的唯一方式,就像在大多數 CPU 上一樣,它會創建存儲順序。)

這種轉換是否有可能導致執行 RMW 操作的線程加載比最新值更舊的值?

特別是在 x86 上,很容易推理。 x86 有一個強有序的內存模型(TSO = 總存儲順序 = 程序順序 + 帶有存儲轉發的存儲緩沖區)。

腳注 1: std::thread可以運行的所有內核都具有一致的緩存。 適用於所有 ISA 的所有真實 C++ 實現,而不僅僅是 x86-64。 有一些具有獨立 CPU 的異構板,在沒有緩存一致性的情況下共享內存,但同一進程的普通 C++ 線程不會跨這些不同的內核運行。 有關更多詳細信息,請參閱此答案

暫無
暫無

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

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