愛伊米

【我讀】Rust 語言應該使用什麼記憶體模型?

“睡前一篇。

原文:https://paulmck。livejournal。com/66175。html[1]

引子

《深入理解並行程式設計》[2]作者 Paul E。 McKenney 最近幾天寫了這篇文章。可以透過知乎這篇文章《深入理解並行程式設計》筆記[3]來了解下這本書。

Paul 日常應該工作於 Facebook,擁有 30 年的 Cpp 併發工作經驗,是Linux 核心記憶體模型(LKMM)[5]的主要維護者。他寫這篇文章的初衷是想為 Rust 社群的併發相關日常工作提供一個良好的起點。Paul 非常關注 Rust 進入 Linux 核心這件事,他在之前的部落格文章中提出一個見解:在 Linux 核心中減少 Unsafe Rust 程式碼的一種方法就是將 Unsafe 操作下推到原子操作、記憶體屏障和鎖原語中。

他的部落格裡也寫了很多 Rust 和 Linux 的文章:https://paulmck。livejournal。com/tag/rust[6]

本篇是我學習這篇文章過程中的閱讀記錄,內容不僅僅是這篇文章,同時也參考了文章中所提及的一些資料。

Rust 當前的記憶體模型

在 Rust 標準庫  模組寫道:“These orderings are the same as theC++20 atomic orderings[7]。” 。意思就是說, Rust 現在的記憶體順序採用的是 C++ 20 的原子記憶體順序。

在 Rust nomicon 一書中的`Atomics` 章節[8], 談到,實際上這個模型相當複雜,並且已知有幾個缺陷[9]。

Rust 很明顯地只是從 C++20 繼承了原子的記憶體模型。這並不是因為該模型特別出色或易於理解。事實上,這個模型非常複雜,並且已知有幾個缺陷。相反,這是對每個人都不擅長原子建模這一事實的務實讓步。至少,我們可以從圍繞 C/C++ 記憶體模型的現有工具和研究中受益。(你會經常看到這個模型被稱為“C/C++11”或“C11”。C 只是複製了 C++ 記憶體模型;C++11 是該模型的第一個版本,但它已經收到了一些從那以後的錯誤修正。)

試圖完全解釋本書中的模型是相當無望的。它是根據引發瘋狂的因果關係圖來定義的,需要一整本書才能以實用的方式正確理解。如果你想要所有的細節,你應該檢視C++ 規範[10]。

C++ 記憶體模型從根本上是試圖彌合我們想要的語義、編譯器想要的最佳化以及我們的硬體想要的不一致的混亂之間的差距。我們只想編寫程式,讓它們完全按照我們說的去做。

Linux 核心記憶體模型(LKMM)

LKMM 中最容易令人生畏的地方包括:

控制依賴[11]

地址和資料相關性[12]

控制依賴

在許多弱序架構的組合語言層面上,條件性分支充當了一個非常弱、非常便宜但非常有用的記憶體屏障指令。它命令任何返回值進入條件程式碼的,在分支指令完成後執行的所有之前,無論分支是否被採納。也有一個條件移動指令(),提供類似的排序。

因為條件分支的排序屬性涉及從載入()到分支以及從分支到儲存()的依賴關係,並且因為分支是控制流指令,所以這種排序被稱為控制依賴。

因為編譯器不理解它們,所以控制依賴非常脆弱。但是它們的成本非常低,因此它們被用於 Linux 核心中一些非常重要的快速路徑。

Rust 可以透過多種方式處理控制依賴:

簡單的解決方案是將控制依賴項的提升到。這有效,但在某些架構上增加了指令開銷,並不必要地限制了所有架構上的編譯器最佳化(但公平地說,在使用連結時最佳化構建時正是這樣做的)。另一個困難是確定(無論是手動還是自動)確切地需要提升哪些呼叫。

一個更簡單的解決方案是將包含控制依賴項的程式碼分類為 Rust 範圍之外的核心 Linux 核心程式碼。由於在 Linux 核心中很少使用控制依賴項,因此 Rust 採取這種方法不會損失太多。此外,還有可能建立更高級別的 C 語言原語,其中包含所需的控制依賴項,然後將其包裝起來以供 Rust 語言使用。

從 開發人員的角度來看,最好的方法是讓 Rust 強制執行記錄的程式碼樣式限制。然而,這種方法有可能被證明是不平凡(non-trivial)的。

等待編譯器後端瞭解控制依賴項。這可能需要等待一段時間,尤其是考慮到在 標準的當前命名法中甚至難以定義控制依賴項。

地址和資料相關性

地址依賴涉及一個載入,它的返回值直接或間接決定了後面載入或儲存的地址,這導致較早的載入在後面的載入或儲存之前被排序。資料依賴涉及一個載入,它的返回值直接或間接決定了後面的儲存儲存的值,這導致載入在儲存之前被排序。這些被 (,讀複製更新)大量使用。儘管它們不像控制依賴那樣脆弱,但編譯器仍然不知道它們。因此,仍然需要小心,如`rcu_dereference。rst`[13]Linux 核心編碼指南中所見。與控制依賴一樣,地址和資料依賴的開銷非常低,但與控制依賴不同,它們在 Linux 核心中被大量使用。

Rust 記憶體模型應該將其對 Linux 核心原子操作的支援限制為提供排序的那些。這些將是返回值的非寬鬆(non-relaxed)讀-修改-寫 (RMW) 原子操作以及非返回值的 RMW 原子操作的和變體。允許無序 RMW 操作與組合記憶體屏障的組合也可能有意義,例如,後跟,但將它們組合包裝為單個 Rust 可訪問原語會更有意義。這個組合的 Rust 原語將不再是無序的,因此可以作為一個有序的單元包含在 Rust 記憶體模型中。或者,無序原子操作(relax)可能會降級為 Rust 的模式。

因此,從 LKMM 開始,我們得到了一個支援有序原子操作和鎖定的模型,可能包括模式下的無序原子操作。

C++ 記憶體模型

Cpp 的 會導致 的值出現。所以,Rust 中 建議只允許在 程式碼中使用。

而安全的 Rust 程式碼應該允許使用這四個順序:、``memory_order_releasememory_order_acq_rel` 。

建議

從 Linux 核心記憶體模型 和 Cpp 記憶體模型的一些問題,作者對 Rust 中的記憶體順序改進提出以下建議:

可以在 和 下使用 鎖原語。

、、``memory_order_releasememory_order_acq_relSafeUnSafe` 下使用。

和 只可在  下使用。

在安全模式下采用記憶體模型中沒有問題的部分,其餘部分採用模式。這種方法允許人們用 Rust 編寫每天的併發演算法,並確信所產生的程式碼在未來仍然可以工作。

就看 Rust 社群如何選擇了。

來自 Rust 社群的聲音

有另一位讀者對他給 Rust 社群的建議做了如下回復(以下摘要):

讀取的 OOTA 行為本身不會違反 Rust 的任何記憶體安全保證 —— 一般而言, 記憶體操作仍不會引起未定義的行為。因此,沒有真正的理由從 Rust 安全程式碼中排除更多“異乎尋常”的操作。

的操作也有實際的編譯器支援,並且在許多對效能非常重要的情況下很有用,尤其是在弱排序的硬體上,因此出於純粹的教學原因將它們全部升級為更昂貴的操作似乎很愚蠢。

所以這就是 Rust 現在的位置:它基於 記憶體模型不是因為人們認為沒有更好的東西(尤其是在 周圍),也不是因為人們不接受顯式記憶體或控制依賴,而是因為那些是編譯器目前提供的實際“工作”並且可以證明事情的原語。

至於“Rust 現在真的需要它的記憶體模型嗎”:我認為是的,它確實需要。但它不需要成為最終的記憶體模型。

仁者見仁,智者見智吧。

參考

LINUX 核心記憶體屏障[14]

LINUX 核心記憶體屏障 【中文版】[15]

參考資料[1]

https://paulmck。livejournal。com/66175。html:https://paulmck。livejournal。com/66175。html

[2]

《深入理解並行程式設計》:https://mirrors。edge。kernel。org/pub/linux/kernel/people/paulmck/perfbook/perfbook。html

[3]

《深入理解並行程式設計》筆記:https://zhuanlan。zhihu。com/p/56873613

[4]

PDF 版本下載:https://mirrors。edge。kernel。org/pub/linux/kernel/people/paulmck/perfbook/perfbook-e2。pdf

[5]

Linux 核心記憶體模型(LKMM):https://github。com/torvalds/linux/tree/master/tools/memory-model

[6]

https://paulmck。livejournal。com/tag/rust:https://paulmck。livejournal。com/tag/rust

[7]

C++20 atomic orderings:https://en。cppreference。com/w/cpp/atomic/memory_order

[8]

章節:https://doc。rust-lang。org/stable/nomicon/atomics。html

[9]

幾個缺陷:http://plv。mpi-sws。org/c11comp/popl15。pdf

[10]

C++ 規範:https://en。cppreference。com/w/cpp/atomic/memory_order

[11]

控制依賴:https://paulmck。livejournal。com/63151。html

[12]

地址和資料相關性:https://paulmck。livejournal。com/63316。html

[13]

:https://www。kernel。org/doc/Documentation/RCU/rcu_dereference。rst

[14]

LINUX 核心記憶體屏障:https://www。kernel。org/doc/Documentation/memory-barriers。txt

[15]

LINUX 核心記憶體屏障 【中文版】:https://maple-leaf-0219。github。io/2020/linux%E5%86%85%E6%A0%B8%E4%B8%AD%E7%9A%84%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C-%E8%AF%91/