高性能编程:三级缓存(LLC)访问优化
作者:ciuwaalu,騰訊 TEG 后臺開發(fā)工程師
AMD 服務器,多線程應用綁核,選取不同的 CPU 核,性能差距可達50%。
最近有幸因項目拿到一臺?AMD EPYC 系列測試服務器,發(fā)現(xiàn)了一些奇怪的現(xiàn)象。
這臺測試服務器擁有雙路 AMD EPYC ?7552 ?處理器,屬于第二代 Rome(Zen2)架構,單路 48 個物理核,雙路總計 192 個邏輯核(線程),有兩個 NUMA 節(jié)點。
為了進行測試,預先編寫了一個簡單的多線程程序:
兩個線程,分別為生產(chǎn)者、消費者,模擬 route-worker 模型;
三個線程,分別為生產(chǎn)者、轉發(fā)者、消費者,模擬 pipeline 模型。
線程間采用無鎖隊列通信。生產(chǎn)者依次寫入 1 ~100000000,消費者取出數(shù)字求和。線程每次寫入或讀取隊列數(shù)據(jù)后執(zhí)行一些無意義循環(huán)用于消耗時間,模擬業(yè)務邏輯。
所有線程分別綁核,避免線程遷移導致 Cache 抖動,且綁定的核心屬于同一個 CPU。所有隊列均在這個 CPU 的本地內(nèi)存上進行分配,避免跨 NUMA 的遠程內(nèi)存訪問。
奇怪的現(xiàn)象
測試發(fā)現(xiàn),線程綁到不同的核上,有顯著的性能差異:
綁核說明:
核 #4 #5 #6 #8 #12 #100 均為同一個 CPU,不存在跨 NUMA 訪問內(nèi)存的情況;
核 #4 #100 是一對 SMT 核心,即同一個物理核虛擬出來的兩個邏輯核;
黃條涉及的核 #48 屬于另一個 CPU,存在跨 NUMA 訪問內(nèi)存的情況,僅供對比。
測試結果反映了一個很奇怪的現(xiàn)象:線程綁核,在同一個 NUMA 選取不同的核心,性能差距竟然達到 50%(route-worker 模型 #4#5 vs #4#8)甚至 140%(pipeline 模型 #4#5#6 vs #4#8#12)。
這究竟是為什么呢?
復雜的內(nèi)存層次模型
這要從內(nèi)存層次說起。通常,根據(jù)延遲時間從小到大,內(nèi)存層次可以劃分為:(1)L1,一級緩存;(2)L2,二級緩存;(3)L3,又叫 LLC,三級緩存;(4)內(nèi)存。
在具體實現(xiàn)上,傳統(tǒng)的 Intel 至強系列模型比較簡單:
每個物理核虛擬出兩個邏輯核(TR1/TR2,TR3/TR4)
每個物理核獨有 L1 和 L2
所有物理核共享 L3
這就解釋了一些高性能程序開發(fā)的優(yōu)化策略:
避免跨 NUMA 的遠程內(nèi)存訪問,除了降低訪問延遲,對 L3 也更友好
將線程綁核,避免 Cache 抖動,具體是避免 L1 和 L2 的抖動
共享 L3 的存在是透明的,軟件上不關心,也無法關心
這一切,在 AMD 的體系結構中發(fā)生了變化。
AMD 于 2017 年發(fā)布了 Zen 架構,其中一個重要的設計原則是:一塊 CPU 由多個 CCX(CPU Complex)堆疊而成。那么,CCX 是什么呢?簡單來說,CCX 實際上就是 4 個物理核(8 個邏輯核)+ L3。CCX 通過 IF 總線與 IO Die 連接(Rome),實現(xiàn) CCX 間互通以及與內(nèi)存、IO 的通信。
圖片來源:https://frankdenneman.nl/2019/10/14/amd-epyc-naples-vs-rome-and-vsphere-cpu-scheduler-updates/
所以,AMD EPYC 的內(nèi)存模型就和傳統(tǒng)模型有了很大區(qū)別:L3 并不由所有物理核共享,而是由同一個 CCX 內(nèi)的 4 個物理核共享。與 NUMA 引入的“遠程內(nèi)存”概念類似,CCX 引入了“遠程 L3”的概念。
在網(wǎng)上找到一個訪問延遲表,供參考:
| 一個 CPU 周期(2.3GHz 主頻) | 0.4 ns |
| 訪問 L1 | 1.6 ns |
| 訪問 L2 | 4.8 ns |
| 訪問 L3 | 15.2 ns |
| 訪問遠程 L3 | 63 ns |
| 訪問本地內(nèi)存 | 75 ns |
| 訪問遠程內(nèi)存 | 130 ns |
結論與優(yōu)化建議
結論是,在 AMD 服務器下,如果要獲得更高的性能,要針對 L3 進行優(yōu)化,方法為:把一組任務(線程、進程)綁定到同一個 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 數(shù)據(jù)失效,導致讀隊列時必須要從內(nèi)存中讀取,所以性能差。
最后,可以通過:
perf stat -e r510143,r510243,r510843,r511043,r514043 ./xxx 查看 L3 的訪問情況,PMC Code 來自 AMD的官方文檔:
可以看到綁核 #4 #8 讀取內(nèi)存次數(shù)幾乎是綁核 #4 #5 的 3 倍。
總結
以上是生活随笔為你收集整理的高性能编程:三级缓存(LLC)访问优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spark源码和调优简介 Spark C
- 下一篇: 腾讯汤道生:开源已成为许多技术驱动型产业