簡體   English   中英

非原子<> 指針上的原子操作能否比原子<> 更安全且更快?

[英]Can atomic operations on a non-atomic<> pointer be safe and faster than atomic<>?

我有十幾個線程在讀取一個指針,而一個線程可能會在一小時左右更改一次該指針。

讀者是超級、超級、超級時間敏感的。 我聽說atomic<char**>或進入主 memory 的速度是什么,我想避免這種情況。

在現代(比如 2012 年及以后)服務器和高端台式機 Intel 中,如果正常讀寫,是否可以保證 8 字節對齊的常規指針不撕裂? 我的一個測試運行一個小時沒有看到眼淚。

否則,如果我以原子方式寫入並正常讀取,會更好(或更糟)嗎? 例如通過將兩者結合起來?

請注意,還有其他關於混合原子和非原子操作的問題,這些問題沒有指定 CPU,並且討論轉向語言律師。 這個問題不是關於規范,而是關於究竟會發生什么,包括我們是否知道在規范未定義的情況下會發生什么。

x86 永遠不會將 asm 加載或存儲到對齊的指針寬度值。 這個問題的那一部分,以及您的另一個問題( 現代英特爾上的 C++11:我是瘋了還是非原子對齊的 64 位加載/存儲實際上是原子的? )都是為什么 integer 分配在自然對齊的變量原子上的重復x86?

這就是為什么atomic<T>編譯器實現起來如此便宜,以及為什么使用它沒有缺點的部分原因。

在 x86 上讀取atomic<T>的唯一實際成本是,它無法跨多次讀取同一 var 優化到寄存器中。 但是無論如何你都需要讓你的程序正常工作(即讓線程注意到指針的更新)。 在非 x86 上,只有mo_relaxed與普通 asm 負載一樣便宜,但 x86 強大的 memory model 甚至可以使 seq_cst 負載便宜。

如果在一個 function 中多次使用指針,做T* local_copy = global_ptr; 因此編譯器可以將local_copy在寄存器中。 將此視為從 memory 加載到私有寄存器中,因為這正是它的編譯方式。 原子對象上的操作不會優化,因此如果您想在每個循環中重新讀取一次全局指針,請以這種方式編寫您的源代碼。 或者一旦在循環之外:以這種方式編寫您的源代碼並讓編譯器管理本地變量。


顯然你一直試圖避免atomic<T*>因為你對std::atomic::load()純加載操作的性能有一個巨大的誤解。 std::atomic::store()會慢一些,除非您使用 memory_order 釋放或放松,但在 x86 上 std::atomic 沒有額外的 seq_cst 加載成本。

在這里避免atomic<T*>沒有性能優勢。 它可以安全、便攜地完成您所需要的工作,並為您的主要閱讀用例提供高性能。 每個讀取它的核心都可以訪問其私有 L1d 緩存中的副本。 寫入使行的所有副本無效,因此寫入者擁有獨占所有權 (MESI),但從每個內核的下一次讀取將獲得一個共享副本,該副本可以再次在其私有緩存中保持熱狀態。

(這是一致性緩存的好處之一:讀者不必一直檢查某個共享副本。寫入者在寫入之前被迫確保任何地方都沒有陳舊的副本。這一切都是由硬件完成的,而不是使用軟件 asm 指令。我們運行多個 C++ 線程的所有 ISA 都具有緩存一致的共享 memory,這就是為什么volatile可以用於滾動您自己的原子( 但不要這樣做),就像人們在 Z656473FZ6E7DAB566473FZ6E7DAB56473FZ6E7DAB566473FZ6E7DAB56473FZ6E7DAB4 . 或者就像你試圖在使用volatile的情況下做的那樣,它只適用於調試版本。絕對不要那樣做!)

原子加載編譯為編譯器用於其他所有內容的相同指令,例如mov 在 asm 級別,每個對齊的加載和存儲都是一個原子操作(對於 2 個大小的冪,最多 8 個字節)。 atomic<T>需要阻止編譯器假設沒有其他線程在訪問之間寫入 object。

(與純加載/純存儲不同, 整個 RMW 的原子性不會免費發生ptr_to_int++將編譯為lock add qword [ptr], 4 。但在無競爭的情況下,這仍然比緩存未命中要快得多DRAM,只需要一個“緩存鎖”在核心內擁有該行的獨占所有權。就像每次操作 20 個周期,如果你什么都不做,只是在 Haswell 上背靠背( https://agner.org/optimize / ),但其他代碼中間只有一個原子 RMW 可以與周圍的 ALU 操作很好地重疊。)

與需要 RWlock 的任何內容相比,純只讀訪問是使用原子的無鎖代碼真正閃耀的地方- atomic<>讀取器不會相互競爭,因此讀取端可以完美地擴展到這樣的用例(或 RCU 或一個序列鎖)。

在 x86 上, seq_cst加載(默認排序)不需要任何屏障指令,這要歸功於 x86 的硬件內存排序 model(程序順序加載/存儲,加上帶有存儲轉發的存儲緩沖區)。 這意味着您可以在使用指針的讀取端獲得完整的性能,而無需削弱以acquireconsume memory 訂單。

如果存儲性能是一個因素,您可以使用std::memory_order_release因此存儲也可以是普通的mov ,而無需使用mfencexchg耗盡存儲緩沖區。


我聽說atomic<char**>或進入主 memory 的速度是多少

無論你讀什么都誤導了你。

即使在內核之間獲取數據也不需要進入實際的 DRAM,只需要共享最后一級緩存即可。 由於您使用的是 Intel CPU,因此 L3 緩存是緩存一致性的后盾。

在核心寫入緩存行之后,它仍將位於 MESI 修改后的 state 中的私有 L1d 緩存中(並且在所有其他緩存中無效;這就是 MESI 保持緩存一致性的方式 = 任何地方都沒有行的陳舊副本)。 因此,來自該高速緩存行的另一個核心上的負載將在私有 L1d 和 L2 高速緩存中丟失,但 L3 標簽會告訴硬件哪個核心擁有該行的副本。 一條消息通過環形總線到達該核心,使其將線路寫回 L3。 從那里可以將其轉發到仍在等待加載數據的核心。 這幾乎就是內核間延遲的衡量標准——在一個內核上存儲和在另一個內核上獲得價值之間的時間。

這所花費的時間(內核間延遲)大致類似於 L3 緩存中未命中的負載並且必須等待 DRAM,例如 40ns 對 70ns,具體取決於 CPU。 也許這就是你讀到的。 (多核 Xeon 在環形總線上的跳數更多,內核之間以及從內核到 DRAM 的延遲更多。)

但這僅適用於寫入后的第一次加載。 數據由加載它的內核上的 L2 和 L1d 緩存以及 L3 的共享 state 緩存。 在那之后,任何頻繁讀取指針的線程都會使該行在運行該線程的核心上的快速私有 L2 甚至 L1d 緩存中保持熱狀態。 L1d 緩存有 4-5 個周期的延遲,每個時鍾周期可以處理 2 個負載。

並且該行將在 L3 中的 Shared state 中,任何其他內核都可以命中,因此只有第一個內核支付全部內核間延遲損失。

(在 Skylake-AVX512 之前,Intel 芯片使用包含 L3 緩存,因此 L3 標簽可以用作窺探過濾器,用於內核之間基於目錄的緩存一致性。如果某行在某些私有緩存中的 Shared state 中,則它在 Shared state 中也有效在 L3 中。即使在 L3 緩存不保持包含屬性的 SKX 上,數據在內核之間共享后也會在 L3 中存在一段時間。)

在調試版本中,每個變量都在 C++ 語句之間存儲/重新加載到 memory。 這並不(通常)比正常優化構建慢 400 倍這一事實表明,當 memory 訪問緩存時,在非競爭情況下訪問並不會太慢。 (將數據保存在寄存器中比 memory 更快,因此調試構建通常非常糟糕。如果您使用memory_order_relaxed使每個變量atomic<T> ,這將有點類似於沒有優化的編譯,除了像++之類的東西)。 為了清楚起見,我並不是atomic<T>使您的代碼以調試模式速度運行。 每次源提到它時,都需要從 memory (通過緩存)重新加載可能已異步更改的共享變量,而atomic<T>會這樣做。


正如我所說,讀取atomic<char**> ptr將編譯為 x86 上的mov負載,沒有額外的柵欄,與讀取非原子 object 完全相同。

除了它會阻止一些編譯時重新排序,並且像volatile一樣阻止編譯器假設值永遠不會改變並將負載提升到循環之外。 它還阻止編譯器發明額外的讀取。 https://lwn.net/Articles/793253/


我有十幾個線程在讀取一個指針,而一個線程可能會在一小時左右更改一次該指針。

您可能需要 RCU,即使這意味着為每個非常不頻繁的寫入復制一個相對較大的數據結構。 RCU 使閱讀器真正成為只讀的,因此閱讀端縮放是完美的。

C++ 11/14/17 的其他答案:讀者/作者鎖……沒有讀者鎖? 建議涉及多個 RWlock 的事情,以確保讀者總是可以拿到一個。 這仍然涉及所有讀者都爭相修改的某個共享緩存行上的原子 RMW。 如果您有讀取 RWlock 的讀取器,他們可能因為內核間延遲而停止,因為他們將包含鎖的緩存線放入 MESI Modified state。

(Hardware Lock Elision 用於解決避免讀取器之間爭用的問題,但已被所有現有硬件上的微碼更新禁用。)

暫無
暫無

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

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