深挖NUMA
首先列出開源小站之前相關的幾篇帖子:
- Linux的NUMA機制
- NUMA對性能的影響
- cgroup的cpuset問題
這次,就來深入了解下NUMA。
就如之前說的,在若干年前,對於x86架構的計算機,那時的內存控制器還沒有整合進CPU,所有內存的訪問都需要通過北橋晶元來完成。此時的內存訪問如下圖所示,被稱為UMA(uniform memory access, 一致性內存訪問 )。這樣的訪問對於軟體層面來說非常容易實現:匯流排模型保證了所有的內存訪問是一致的,不必考慮由不同內存地址之前的差異。
之後的x86平台經歷了一場從「拼頻率」到「拼核心數」的轉變,越來越多的核心被儘可能地塞進了同一塊晶元上,各個核心對於內存帶寬的爭搶訪問成為了瓶頸;此時軟體、OS方面對於SMP多核心CPU的支持也愈發成熟;再加上各種商業上的考量,x86平台也順水推舟的搞了NUMA(Non-uniform memory access, 非一致性內存訪問)。
在這種架構之下,每個Socket都會有一個獨立的內存控制器IMC(integrated memory controllers, 集成內存控制器),分屬於不同的socket之內的IMC之間通過QPI link通訊。
然後就是進一步的架構演進,由於每個socket上都會有多個core進行內存訪問,這就會在每個core的內部出現一個類似最早SMP架構相似的內存訪問匯流排,這個匯流排被稱為IMC bus。
於是,很明顯的,在這種架構之下,兩個socket各自管理1/2的內存插槽,如果要訪問不屬於本socket的內存則必須通過QPI link。也就是說內存的訪問出現了本地/遠程(local/remote)的概念,內存的延時是會有顯著的區別的。這也就是之前那篇文章中提到的為什麼NUMA的設置能夠明顯的影響到JVM的性能。
回到當前世面上的CPU,工程上的實現其實更加複雜了。以Xeon 2699 v4系列CPU的標準來看,兩個Socket之之間通過各自的一條9.6GT/s的QPI link互訪。而每個Socket事實上有2個內存控制器。雙通道的緣故,每個控制器又有兩個內存通道(channel),每個通道最多支持3根內存條(DIMM)。理論上最大單socket支持76.8GB/s的內存帶寬,而兩個QPI link,每個QPI link有9.6GT/s的速率(~57.6GB/s)事實上QPI link已經出現瓶頸了。
嗯,事情變得好玩起來了。
核心數還是源源不斷的增加,Skylake桌面版本的i7 EE已經有了18個core,下一代的Skylake Xeon妥妥的28個Core。為了塞進更多的core,原本核心之間類似環網的設計變成了複雜的路由。由於這種架構上的變化,導致內存的訪問變得更加複雜。兩個IMC也有了local/remote的區別,在保證兼容性的前提和性能導向的糾結中,系統允許用戶進行更為靈活的內存訪問架構劃分。於是就有了「NUMA之上的NUMA」這種妖異的設定(SNC)。
回到Linux,內核mm/mmzone.c , include/linux/mmzone.h文件定義了NUMA的數據結構和操作方式。
Linux Kernel中NUMA的調度位於kernel/sched/core.c函數int sysctl_numa_balancing
- 在一個啟用了NUMA支持的Linux中,Kernel不會將任務內存從一個NUMA node搬遷到另一個NUMA node。
- 一個進程一旦被啟用,它所在的NUMA node就不會被遷移,為了儘可能的優化性能,在正常的調度之中,CPU的core也會儘可能的使用可以local訪問的本地core,在進程的整個生命周期之中,NUMA node保持不變。
- 一旦當某個NUMA node的負載超出了另一個node一個閾值(默認25%),則認為需要在此node上減少負載,不同的NUMA結構和不同的負載狀況,系統見給予一個延時任務的遷移——類似於漏杯演算法。在這種情況下將會產生內存的remote訪問。
- NUMA node之間有不同的拓撲結構,各個 node 之間的訪問會有一個距離(node distances)的概念,如numactl -H命令的結果有這樣的描述:node distances:
node 0 1 2 3
0: 10 11 21 21 1: 11 10 21 21 2: 21 21 10 113: 21 21 11 10
可以看出:0 node 到0 node之間距離為10,這肯定的最近的距離,不提。0-1之間的距離遠小於2或3的距離。這種距離方便系統在較複雜的情況下選擇最合適的NUMA設定。
上圖記錄了某個Benchmark工具,在開啟/關閉NUMA功能時QPI帶寬消耗的情況。很明顯的是,在開啟了NUMA支持以後,QPI的帶寬消耗有了兩個數量級以上的下降,性能也有了顯著的提升!
通常情況下,用戶可以通過numactl來進行NUMA訪問策略的手工配置,cgroup中cpuset.mems也可以達到指定NUMA node的作用。以numactl命令為例,它有如下策略:
- –interleave=nodes //允許進程在多個node之間交替訪問
- –membind=nodes //將內存固定在某個node上,CPU則選擇對應的core。
- –cpunodebind=nodes //與membind相反,將CPU固定在某(幾)個core上,內存則限制在對應的NUMA node之上。
- –physcpubind=cpus //與cpunodebind類似,不同的是物理core。
- –localalloc //本地配置
- –preferred=node //按照推薦配置
對於某些大內存訪問的應用,比如Mongodb,將NUMA的訪問策略制定為interleave=all則意味著整個進程的內存是均勻分布在所有的node之上,進程可以以最快的方式訪問本地內存。
講到這裡似乎已經差不多了,但事實上還沒完。如果你一直按照這個「北橋去哪兒了?」的思路理下來的話,就有了另一個問題,那就是之前的北橋還有一個功能就是PCI/PCIe控制器,當然原來的南橋,現在的PCH一樣也整合了PCIe控制器。事實上,在PCIe channel上也是有NUMA親和性的。
比如:查看網卡enp0s20f0u5的NUMA,用到了netdev:<設備名稱>這種方式。
[root@local ~]# numactl --prefer netdev:enp0s20f0u5 --showpolicy: preferredpreferred node: 0physcpubind: 0cpubind: 0nodebind: 0membind: 0 1 2 3
或者一個PCI address 為00:17的SATA控制器,用到了pci:<PCI address>
[root@local~]# numactl --prefer pci:00:17 --showpolicy: preferredpreferred node: 0physcpubind: 0cpubind: 0nodebind: 0membind: 0 1 2 3
還有block/ip/file的方式查看NUMA affinity,這裡也就不再累述了。
綜上,感覺上現在的伺服器,特別是多socket的伺服器架構更像是通過NUMA共享了內存;進而通過共享內存從而共享外設的多台主機。
最後還是那句:那種一個業務就能跑滿整台server的時代是再也回不來了!
--原發佈於2017/10/31 深挖NUMA - 開源小站
推薦閱讀: