多核心、一致性與執行緒層級平行

非均勻記憶體存取(NUMA)

/ NOO-mah /

想像一個大型開放式辦公室分成幾翼,每一翼旁邊都有自己的檔案櫃。拿自己這一翼的櫃子很快;從三翼之外的櫃子取一份卷宗,就得走一段長路。每個人仍然搆得到每一個櫃子——這是一套共享的檔案系統——但要花多久,取決於卷宗住在哪、相對於你坐在哪。非均勻記憶體存取(NUMA)就是這樣建造的共享記憶體機器:記憶體被切成接到不同核心的記憶庫,一個核心存取它附近的(本地)記憶體很快、存取遠處的(遠端)記憶體很慢。

結構如下。一台大型多處理器被分成數個節點;每個節點把若干核心、一片主記憶體、和它自己的記憶體控制器綁在一起。記憶體仍然是單一的共享位址空間——任何核心都能讀任何位址——但對自己節點記憶體的存取是本地的、快的,而對另一個節點所歸屬的記憶體的存取,必須越過互連,是遠端的、較慢的,常慢上兩倍或更多。這就是它叫「非均勻」的原因:延遲取決於哪個節點擁有那筆資料。它與 UMA(小型 SMP 機器的均勻存取模型)相反。

NUMA 之所以存在,是因為嚴格的均勻存取無法擴展:超過二、三十個核心後,你無法讓每個人都同等快速地存取一個中央記憶體,於是你把記憶體分散,並接受距離現在會有影響。實際後果是:資料放置與執行緒放置成了效能關鍵——把一個執行緒的熱資料留在它自己節點的記憶體裡,並把該執行緒釘在那裡。誠實的陷阱是:NUMA 大致上是隱形的,直到它咬你:程式不論如何都正確,但把一個執行緒的資料散落到遠端節點,它就可能比放置得當的相同程式碼慢上許多。NUMA 感知的配置與排程,是大型伺服器奪回那份失去速度的方法。

一台雙插槽伺服器,每個插槽是一個 NUMA 節點、有自己的記憶體。插槽 0 上的一個執行緒讀取存在插槽 0 記憶體的資料可能要 100 奈秒;讀取同類但歸屬於插槽 1 記憶體的資料可能要 150 奈秒,因為請求要越過插槽間的連結。同樣的程式、同樣的正確性——但把資料放在錯的節點上,吞吐量就下滑。

本地記憶體快、遠端記憶體較慢;正確性不變,但放置決定速度——這就是 NUMA 的精髓。

NUMA 從不改變程式的結果,只改變它的速度,所以它的代價在原始碼裡是隱形的。大型多核心與多插槽系統是 NUMA,不是教科書上那種均勻的 SMP——好的資料與執行緒放置,重要性可與演算法本身相當。

又称
NUMAnon-uniform memory architecture非統一記憶體存取