[英][[carries_dependency]] what it means and how to implement
我在這篇SO帖子中閱讀了 [[carries_dependency]]。
但我無法理解的是接受的答案中的以下句子:
“特別是,如果將使用 memory_order_consume 讀取的值傳遞給函數,然后沒有 [[carries_dependency]],那么編譯器可能必須發出內存柵欄指令以保證支持適當的內存排序語義。如果參數用 [[carries_dependency]] 注釋,那么編譯器可以假設函數體將正確攜帶依賴項,並且可能不再需要此圍欄。
類似地,如果一個函數返回一個加載了 memory_order_consume 的值,或者從這樣的值派生,那么如果沒有 [[carries_dependency]],編譯器可能需要插入一個柵欄指令來保證適當的內存排序語義得到支持。 有了 [[carries_dependency]] 注釋,這個圍欄可能不再是必要的,因為調用者現在負責維護依賴樹。”
讓我們一步一步來:
“如果將使用 memory_order_consume 讀取的值傳遞給函數,然后沒有 [[carries_dependency]],那么編譯器可能必須發出內存柵欄指令以保證支持適當的內存排序語義。”
因此,對於釋放-消耗內存模型中的原子變量,當原子變量作為參數傳遞給函數時,編譯器將引入柵欄硬件指令,以便它始終具有提供給函數的原子變量的最新和更新值。
下一個 -
“如果參數用 [[carries_dependency]] 注釋,那么編譯器可以假設函數體將正確攜帶依賴項,並且可能不再需要這個圍欄。”
這讓我很困惑——原子變量值已經被消耗掉了,然后這個函數攜帶了什么依賴?
相似地 -
“如果一個函數返回一個加載了 memory_order_consume 的值,或者從這樣的值派生,那么如果沒有 [[carries_dependency]],編譯器可能需要插入一個柵欄指令來保證適當的內存排序語義得到支持。使用 [[ Carry_dependency]] 注釋,這個圍欄可能不再是必要的,因為調用者現在負責維護依賴樹。”
從這個例子中,它不清楚它試圖說明攜帶依賴的意義是什么?
僅供參考, memory_order_consume
(和[[carries_dependency]]
)基本上已被棄用,因為編譯器很難以 C++11 設計規則的方式有效和正確地實現規則。 (和/或因為[[carries_dependency]]
和/或kill_dependency
最終會在所有地方都需要。)參見P0371R1:暫時不鼓勵 memory_order_consume 。
當前的編譯器簡單地將mo_consume
視為mo_acquire
(因此在需要它的 ISA 上,在消耗負載之后立即設置屏障)。 如果您想要無障礙的數據依賴排序的性能,您必須通過使用mo_relaxed
和仔細編碼來欺騙編譯器,以避免可能使編譯器在沒有實際依賴的情況下創建 asm 的事情。 (例如 Linux RCU)。 有關更多詳細信息和鏈接,請參閱C++11:memory_order_relaxed 和 memory_order_consume 之間的區別,以及mo_consume
旨在公開的 asm 功能。
此外, 內存順序消耗 C11 中的使用量。
理解依賴順序(在 asm 中)的概念對於理解這個 C++ 特性是如何設計的基本上是必不可少的。
當 [an] 原子變量作為參數傳遞給函數時,編譯器將引入柵欄硬件指令......
首先,您不會將“原子變量”傳遞給函數; 這甚至意味着什么? 如果您傳遞一個指向原子對象的指針或引用,該函數將從它自己加載,並且該函數的源代碼將使用或不使用memory_order_consume
。
相關的事情是使用 mo_consume 傳遞從原子變量加載的值。 像這樣:
int tmp = shared_var.load(std::memory_order_consume);
func(tmp);
func
可以使用該 arg 作為atomic<int>
數組的索引來執行mo_relaxed
加載。 為了使加載在shared_var.load
之后進行依賴排序,即使沒有內存屏障, func
代碼生成必須確保加載對 arg 具有 asm 數據依賴性,即使 C++ 代碼執行類似tmp -= tmp;
編譯器通常只會將其視為tmp = 0;
(殺死以前的值)。
但是[[carries_dependency]]
會使編譯器在實現諸如array[idx+tmp]
類的東西時仍然引用具有數據依賴性的歸零值。
原子變量值已經被消耗掉了,那么這個函數攜帶的是什么依賴?
“已經消耗”不是一個有效的概念。 consume
而不是acquire
的全部意義在於以后的加載被正確排序,因為它們對mo_consume
加載結果有數據依賴性,讓您避免障礙。 如果您希望在原始加載之后排序,那么以后的每個加載都需要這樣的依賴項; 沒有任何意義可以說一個值“已被消耗”。
如果由於某個函數缺少 Carry_dependency 而最終插入了一個障礙來促進消費獲得,那么以后的函數將不需要另一個障礙,因為您可以說該值“已經獲得”。 (雖然這不是標准術語。您應該在加載后訂購第一個屏障之后說代碼。)
了解 Linux 內核如何處理這個問題可能會很有用,它們的手工原子和它們支持的編譯器集有限。 在https://github.com/torvalds/linux/blob/master/Documentation/memory-barriers.txt 中搜索“依賴”,並注意“控制依賴”之間的區別,如if(flag) data.load()
與像data[idx].load
這樣的數據依賴關系。
IIRC,當依賴項是條件時,即使 C++ 也不保證mo_consume
依賴項排序if(x.load(consume)) tmp=y.load();
.
請注意,編譯器會有時候把一個數據依賴關系到控制依賴性如果只有2例如可能的值。 這會破壞mo_consume
,並且如果該值來自mo_consume
負載或[[carries_dependency]]
函數 arg,則該優化將不被允許。 這就是為什么它難以實施的部分原因; 它需要教授大量關於數據依賴排序的優化過程,而不是僅僅期望用戶編寫不做通常會優化掉的事情的代碼。 (比如tmp -= tmp;
)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.