轉貼 多核環境下的記憶體屏障指令

2021-04-14 01:49:50 字數 2510 閱讀 8399

呵呵,工作小息 ,抽空看了雲風的blog,發現又有好東西值得學習了,由於多核的程式設計之前接觸得比較少,所以有了這樣的文章,還是很感動的。當然不能忘記感謝前輩們百忙之中抽空寫文章,下面是轉貼的內容

周老師那個 session 正好排在我的前面。同一間會議室,而且內容我也頗有興趣。也就順理成章的聽了。講的東西其實滿不錯的,唯一的抱怨是太像在大學裡授課,互動少了點。會場氣氛遠不如後來 andrei 講 lock-free data structures 那麼精彩。

周老師講的這塊內容,正巧我前幾年寫多執行緒安全的記憶體分配器時碰到過,有點研究。加上前幾年對 intel 的東西頗有興趣,便有了發言的衝動 :) 。當時的會場上下文環境正好是有個朋友提問說:實際上,interlockedincrement 的呼叫是多餘的。(事後交換名片得知,提問的這個哥們是來至 google 的程式設計師)

如果換在幾年前,我是贊同這個哥們的觀點的。記得 04 年左右,我們公司內部的 maillist 上曾經有類似的討論。即,在 32 位系統上,寫乙個 dword 本身就是原子的,如果 cpu 可以保證程式邏輯上的執行次序(program ordering),那麼簡單的利用寫操作就可以替代鎖操作。我們在操作完一大片記憶體資料後,只需要在最後更改關鍵的標記字,那麼不需要加鎖也可以保證安全。(還有乙個隱含的前提是資料必須被 32bit 對齊)

btw, 當天晚上 andrei 講 lock-free data structures 時向大家提的問題:那個 hazard list 為什麼要用單向鍊錶實現?大約也是這個意思,因為鍊錶指標可以被原子的修改而無需加鎖。

單核時代它是對的,因為單核 cpu 要求讀寫操作 self-consistent 。多嘴兩句解釋一下,現代 cpu 工作時的指令執行次序(process ordering)是可以不同於程式編制的次序(program ordering)的,即亂序執行技術。這個技術可以極大的提高流水線的工作效率。單核 cpu 保證讀寫操作的 self-consistent 意味著,等到真正讀入運算元據時,資料符合 program ordering 上的正確性。

可問題也出在這裡。隨著多核的發展,為了提高每個核上的流水線效率,多核環境不再保證其安全。在每乙個核上,cpu 內部工作時指令都可能被亂序執行。那麼邏輯次序上後寫入記憶體的資料未必真的最後寫入。核與核之間作為乙個整體看的話,卻不保證 self-consistent 了。

也就是說,如果你不做多餘的防護措施,在乙個核上寫入一批資料後,如果你期望最後寫乙個標記資料表示前面的資料都已經準備好;然後從另乙個核上依靠判斷這個標記位來判定一切資料就緒。這個策略並不可靠。標記位可能被先寫入,但其它資料尚未真正寫入記憶體。

解決的方法是,在標記位被寫入前,強迫 cpu 序列化。interlockedincrement 和它的兄弟們可以提供這種安全性。翻譯成 intel 指令,會發現它在彙編指令前加了 lock 字首。也就是在這些讀寫記憶體的指令在發起時,cpu 會向匯流排發出乙個 lock# 的訊號,阻塞住其它記憶體訪問請求。

但這麼做未免效率太低。這種影響匯流排的指令會隨著核越來越多而變的越來越低效。可以想象,任意乙個核上發起 lock 幾乎會讓所有的核都短暫的停止工作(除非完全不訪問記憶體?)。我們今天只有兩個或四個核,效能影響微乎其微。但是等到機器擁有 32,64 甚至更多核時,就可能相當嚴重了。

ps. 上面這個說法也不全然正確。因為既然記憶體鎖在多執行緒程式設計中運用的非常廣泛,自然在晶元設計上是要做優化的。在 pentium pro 以後,當被訪問記憶體處於 cache 中時,lock# 訊號不會被發到匯流排上,取而代之的是鎖住 cache 。這樣代價會小的多,但是在某些情況下依舊昂貴。(當多個核 cache 住同一塊記憶體時會受影響)

輕量一點的方案是執行一條 cpuid 指令,它也可以保證前面的操作被序列化。到了pentium iii ,intel 在 ia32 指令集中增加了 sfence 指令用來提供更細的控制粒度以更少的代價解決這個問題。在指令序列中插入 sfence 可以保證在此指令之前的寫操作全部完成(非寫操作的指令依舊允許亂序執行)。這樣我們在另乙個核裡讀相同記憶體時,幾乎不會出錯。

在這裡我用了「幾乎」,是因為諸如訪問單項鍊表,判斷標誌資料的程式設計邏輯,對記憶體的讀操作都是上下文相關的。我們可以斷言執行次序不會被亂序執行影響。構造乙個可能因為亂序讀記憶體而有出錯隱患的合適例子不太容易。但是從 pentium 4 開始,嚴格上講,我們需要用 lfence 指令(pentium 3 沒有提供也不需要這條指令)配合使用。它可以保證在此之前的 program ordering 上的讀記憶體操作已經完成(否則邏輯結果可能因為多核間同步 cache 等原因而受到影響)。另外有 mfence 指令粒度粗一點,可以同時保證讀寫記憶體操作都已完成。

本來不打算翻資料,憑印象寫的。寫完這篇 blog 審了一遍,還是擔心留下重大錯誤。就又一次翻閱了 2005 年在 intel **上免費索取的 ia-32 intel architecture software developer's manual volume 3 的 chapter 7 :multiple-processor management 。

核對後,我想大概意思應該寫清楚了,如果有小 bug 還請行家多多包涵。真有興趣把這點東西搞清楚的朋友,莫信我的一家之言,查 intel 的手冊吧,它寫的更加清楚和權威。btw ,不要問我該去哪找,去問 google 。

top指令下的記憶體概念

virt 1 程序 需要的 虛擬記憶體大小,包括程序使用的庫 資料,以及 malloc new 分配的堆空間和分配的棧空間等 2 假如程序新申請 10mb 的記憶體,但實際只使用了 1mb,那麼它會增長 10mb 而不是實際的 1mb使用量。3 virt swap res res 1 程序當前使用的...

linux環境下指令tar的用法

今天急著做 linux高階環境程式設計 給的作業,後來發現課件是.tar結尾的。所以順便學習了下tar指令的所有用法。tar是只是乙個打包指令,根本沒有對文件進行壓縮。這個 tar 可以將很多檔案 打包 成為乙個檔案 甚至是目錄也可以這麼玩。不過,單純的 tar 功能僅是 打包 而已,亦即是將很多檔...

linux基礎指令以及Linux環境下的重要目錄

學習linux就像學習英語一樣,熟練掌握linux指令,就像學習英語單詞一樣。只有學習好英語單詞,才有可能會寫出英語文章,會做英語翻譯。那麼同樣,只有掌握了linux的基礎指令,才能在linux環境下進行網路程式設計,才能很好的熟悉新環境linux。他們的共同點還有,想要掌握他們,必須經常使用這些單...