作者:ciuwaalu,騰訊 TEG 後臺開發工程師
AMD 伺服器,多執行緒應用綁核,選取不同的 CPU 核,效能差距可達50%。
最近有幸因專案拿到一臺 AMD EPYC 系列測試伺服器,發現了一些奇怪的現象。
這臺測試伺服器擁有雙路 AMD EPYC 7552 處理器,屬於第二代 Rome(Zen2)架構,單路 48 個物理核,雙路總計 192 個邏輯核(執行緒),有兩個 NUMA 節點。
為了進行測試,預先編寫了一個簡單的多執行緒程式:
兩個執行緒,分別為生產者、消費者,模擬 route-worker 模型;
三個執行緒,分別為生產者、轉發者、消費者,模擬 pipeline 模型。
執行緒間採用無鎖佇列通訊。生產者依次寫入 1 ~100000000,消費者取出數字求和。執行緒每次寫入或讀取佇列資料後執行一些無意義迴圈用於消耗時間,模擬業務邏輯。
所有執行緒分別綁核,避免執行緒遷移導致 Cache 抖動,且繫結的核心屬於同一個 CPU。所有佇列均在這個 CPU 的本地記憶體上進行分配,避免跨 NUMA 的遠端記憶體訪問。
奇怪的現象
測試發現,執行緒綁到不同的核上,有顯著的效能差異:
綁核說明:
核 #4 #5 #6 #8 #12 #100 均為同一個 CPU,不存在跨 NUMA 訪問記憶體的情況;
核 #4 #100 是一對 SMT 核心,即同一個物理核虛擬出來的兩個邏輯核;
黃條涉及的核 #48 屬於另一個 CPU,存在跨 NUMA 訪問記憶體的情況,僅供對比。
測試結果反映了一個很奇怪的現象:執行緒綁核,在同一個 NUMA 選取不同的核心,效能差距竟然達到 50%(route-worker 模型 #4#5 vs #4#8)甚至 140%(pipeline 模型 #4#5#6 vs #4#8#12)。
這究竟是為什麼呢?
複雜的記憶體層次模型
這要從記憶體層次說起。通常,根據延遲時間從小到大,記憶體層次可以劃分為:(1)L1,一級快取;(2)L2,二級快取;(3)L3,又叫 LLC,三級快取;(4)記憶體。
在具體實現上,傳統的 Intel 至強系列模型比較簡單:
每個物理核虛擬出兩個邏輯核(TR1/TR2,TR3/TR4)
每個物理核獨有 L1 和 L2
所有物理核共享 L3
這就解釋了一些高效能程式開發的優化策略:
避免跨 NUMA 的遠端記憶體訪問,除了降低訪問延遲,對 L3 也更友好
將執行緒綁核,避免 Cache 抖動,具體是避免 L1 和 L2 的抖動
共享 L3 的存在是透明的,軟體上不關心,也無法關心
這一切,在 AMD 的體系結構中發生了變化。
AMD 於 2017 年釋出了 Zen 架構,其中一個重要的設計原則是:一塊 CPU 由多個 CCX(CPU Complex)堆疊而成。那麼,CCX 是什麼呢?簡單來說,CCX 實際上就是 4 個物理核(8 個邏輯核)+ L3。CCX 通過 IF 匯流排與 IO Die 連線(Rome),實現 CCX 間互通以及與記憶體、IO 的通訊。
圖片來源:https://frankdenneman.nl/2019/10/14/amd-epyc-naples-vs-rome-and-vsphere-cpu-scheduler-updates/
所以,AMD EPYC 的記憶體模型就和傳統模型有了很大區別:L3 並不由所有物理核共享,而是由同一個 CCX 內的 4 個物理核共享。與 NUMA 引入的“遠端記憶體”概念類似,CCX 引入了“遠端 L3”的概念。
在網上找到一個訪問延遲表,供參考:
事件 | 延遲 |
---|---|
一個 CPU 週期(2.3GHz 主頻) | 0.4 ns |
訪問 L1 | 1.6 ns |
訪問 L2 | 4.8 ns |
訪問 L3 | 15.2 ns |
訪問遠端 L3 | 63 ns |
訪問本地記憶體 | 75 ns |
訪問遠端記憶體 | 130 ns |
結論與優化建議
結論是,在 AMD 伺服器下,如果要獲得更高的效能,要針對 L3 進行優化,方法為:把一組任務(執行緒、程式)繫結到同一個 CCX 下的核心。
那怎樣才能知道哪些核心是同一個 CCX 呢?可以使用 hwloc-ls 命令:
可以看出:#0 #96 #1 #97 #2 #98 #3 #99 是 4 個物理核 8 個邏輯核,它們共享了 16 MB 的 L3,所以這幾個核屬於同一個 CCX。
因此,綁核的時候,可以綁 #0 #1 #2 #3 #96 #97 #98 #99,又或者 #4 #5 #6 #7 #100 #101 #102 #103,以此類推。
文章開頭的測試結果就很好解釋了:#4 #5 #6 是同一個 CCX,因為它們共享 L3,每次讀寫佇列其實都是讀寫 L3,所以效能高;#4 #8 #12 分屬 3 個不同的 CCX,每次寫佇列,都會使得其它 CCX 的 L3 資料失效,導致讀佇列時必須要從記憶體中讀取,所以效能差。
最後,可以通過:
perf stat -e r510143,r510243,r510843,r511043,r514043 ./xxx 檢視 L3 的訪問情況,PMC Code 來自 AMD的官方文件:
可以看到綁核 #4 #8 讀取記憶體次數幾乎是綁核 #4 #5 的 3 倍。