簡體   English   中英

C++ 原子和 memory_order 與 RDMA

[英]C++ atomics and memory_order with RDMA

當在現代 memory 上使用無鎖單側 RDMA 時,問題出現了,如果數據對象跨越多個緩存行,遠程讀取器如何安全地查看他們的傳入數據。

在 Derecho 開源多播和復制日志記錄庫(在https://GitHub.com/Derecho-Project上)中,我們有這種模式。 寫入器 W 被授予在讀取器 R 中寫入范圍 memory 的權限。Memory 已正確固定和映射。 現在,假設寫入涉及某種跨越許多緩存行的數據向量,這很常見。 我們使用一個守衛:一個遞增的計數器(也在 RDMA 中訪問 memory,但在其他一些緩存行中)。 R 旋轉,觀察計數器……當它看到變化時,它告訴 R “你有一條新消息”,然后 R 讀取向量中的數據。 稍后我們有第二種模式,其中 R 對 W 說,“我已經完成了那條消息,你可以再發送一條。”

我的問題:對於現代 memory 模型,應該使用哪種類型的 C++ 原子來寫入向量的 memory? 這會被表示為寬松的一致性嗎? 我希望我的代碼能夠在 ARM 和 AMD 上運行,而不僅僅是具有強大 TSO memory model 的英特爾。

然后對於我的計數器,當 R 旋轉以等待計數器更新時,我希望如何聲明計數器? 是否需要將其聲明為獲取-釋放原子?

最后,在速度或正確性方面,在 R 觀察到計數器增加后,將所有內容都聲明為寬松的,然后在此處使用 memory_order 柵欄是否有任何優點? 我的想法是,使用第二種方法,我在所有 RDMA memory 上使用最小一致性 model(對所有此類內存使用相同的 model),而且我只需要在觀察到計數器遞增后調用成本更高的 memory_order 柵欄。 所以它只發生一次,在訪問我的向量之前,而每次輪詢線程循環時獲取釋放原子計數器都會觸發 memory 防護機制。 對我來說,這聽起來非常昂貴。

最后的想法引出了另一個問題:我是否也必須將這個 memory 聲明為 volatile,以便 C-編譯器意識到數據可以在其腳下改變,或者編譯器本身可以看到 std::atomic 類型是否足夠聲明? 在 Intel 上,對於總商店排序,TSO 加上 volatile 是絕對需要的。

[編輯:新信息](我想在這里吸引一些幫助!)

一種選擇似乎是將 RDMA memory 區域聲明為 std::atomic<relaxed_consistency> 但是每次我們的謂詞評估線程重新測試守衛時都使用鎖(在 RDMA memory 中,將使用相同的松弛屬性聲明). 我們將保留 C++ volatile 注釋。

原因是使用具有獲取-釋放語義的鎖,memory 一致性硬件將被警告它需要屏蔽先前的更新。 鎖本身(互斥量)可以在謂詞線程的本地聲明,然后將存在於本地 DRAM 中,這很便宜,並且由於這不是任何東西都爭用的鎖,因此鎖定它可能與 test_and_set 一樣便宜,並且unlocking 只是寫入 0。如果謂詞為真,我們觸發的代碼主體在訪問鎖后運行(可能在鎖釋放后),因此我們建立所需的順序以確保硬件將獲取受保護的 object使用實際 memory 次讀取。 但是通過我們的謂詞測試的每個循環——每個“自旋”——我們最終都會對每個謂詞進行鎖獲取/釋放。 所以這會導致一些放緩。

選項二,看似開銷較少,也將 RDMA 區域聲明為 std::atomic 並具有寬松的一致性,但省略了鎖並像我們現在所做的那樣進行測試。 然后,當謂詞測試為真時,我們將執行具有語義的顯式內存柵欄 (std::memory-order)。 我們得到了相同的障礙,但只在謂詞評估為真時才支付成本,因此開銷更少。

但現在我們遇到了一個不同類型的問題。 Intel 有總存儲順序 TSO,並且因為任何線程都執行一些先寫后讀操作,Intel 可能出於預防而被迫從 memory 獲取保護變量,擔心 TSO 可能會被違反。 帶有 volatile 的 C++ 肯定包含獲取指令。 但是在 ARM 和 AMD 上,硬件本身是否有可能在硬件寄存器或其他東西中存儲一些保護變量很長時間,從而導致我們的“類自旋”循環出現極度延遲? 對 ARM 和 AMD 一無所知,這似乎令人擔憂。 但也許你們中有人知道的比我多得多?

好吧,目前似乎缺乏這方面的專業知識。 可能是 std::atomics 選項的新穎性以及關於 ARM 和 AMD 將如何實現寬松一致性的普遍不確定性讓人們很難知道答案,猜測也無濟於事。

據我了解,正確的答案似乎是:

  1. 由於其 TSO(總商店訂單)政策,英特爾不會出現整個問題。 使用 TSO,因為守衛在它守衛的向量之后更新,因此在任何總存儲順序中,守衛最后更新。 看到守衛的變化可以保證接收者將看到更新的向量元素。 此外,AMD 和 ARM 的默認值很可能模仿 TSO。
  2. 通過明確聲明 RDMA memory 區域具有 relaxed_consistency,開發人員選擇了更便宜的 memory model,但承擔了插入 memory 柵欄的義務。 最明顯的方法是在讀取守衛之前獲取鎖,然后在讀取之后釋放鎖。 即使沒有其他線程爭用鎖,這也會產生成本。 首先,鎖定操作本身需要幾個時鍾周期。 但更廣泛地說,鎖定一個隨機互斥鎖會對緩存產生一些未知的影響,因為硬件必須假定鎖實際上是在爭用的,可能已經發生了等待,並且它腳下的值可能已經改變。 這將導致需要量化的成本。
  3. 等價地,守衛可以聲明使用 acquire_release 一致性。 從表面上看,這會創建一個 memory 柵欄,並且任何看到保護值更改的讀者都可以看到用於寫入向量的先前更新。 同樣,成本需要量化。
  4. 也許,可以在謂詞觸發的代碼塊的頂部進行圍欄讀取。 這將使柵欄脫離主謂詞循環,因此柵欄的成本將僅支付一次,並且僅在謂詞實際為真時才支付。

我們還需要在 C++ 中將我們的原子標記為 volatile。事實上,C++ 可能應該注意到何時訪問 std::atomic 類型,並將其視為對 volatile 的訪問。 但是,目前尚不清楚 C++ 編譯器是否正在實施此策略。

暫無
暫無

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

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