博文:https://chanjarster.github.io...node
原文:What every programmer should know about memory, Part 4: NUMA supportgit
先回顧一下Section 2。github
Figure 2.3是最簡單的NUMA形式,處理器能夠有本身的本地內存,訪問本地內存和其餘處理器的本地內存的開銷區別不大,即NUMA factor比較低。多線程
Figure 2.3: Integrated Memory Controllerapp
對於商業機器來講,全部處理器共享訪問相同的內存,即全部處理器共享同一個北橋,致使全部流量都從北橋走,致使北橋成爲瓶頸。雖然能夠用定製化硬件代替北橋,可是內存芯片必須得支持多port才行,可是Multiport RAM很複雜很貴,因此幾乎沒有人會使用。socket
Figure 5.1: Hypercubespost
一個高效的節點(處理器)拓撲是超立方體,它限定了節點數量爲2C,C爲每一個節點的interconnect接口數量。超立方體擁有2n處理器系統的最小直徑(兩節點之間的最大距離)。看Figure 5.1,每一個處理器的直徑爲C,這個是最小值。性能
後面是已知的幾種NUMA的硬件實現,這裏就不寫了。測試
操做系統在分配內存的時候必須將NUMA考慮進去。好比一個進程運行在一個處理器上,那麼爲其所分配的內存應該來自於本地內存,不然全部的code和data都必須訪問遠程內存才能夠。有一些特殊狀況只有在NUMA下才須要考慮。text segment of DSOs一般狀況下在物理內存中只存有一份,但若是DSOs被全部CPU的線程或進程所使用(好比libc
),這就意味着少數處理器以外的全部處理器都要發生遠程訪問。理想狀況下,操做系統會將DSO在每一個處理器的本地物理RAM裏作mirror
,這是一種優化策略,不是一個要求,一般也很難實現。優化
操做系統不該該將一個進程或線程遷移到另外一個處理器上。不過操做系統應該已經作了相關防範,由於遷移發生就意味着cache就要從新抓。若是爲了負載的均衡,必定要把進程或線程從一個處理器遷移到另外一個處理器,操做系統一般會隨便選一個還剩有容量的處理器。不過在NUMA環境下,操做系統的選擇有一點限制:被選擇的新處理器內存訪問開銷不能比舊處理器大。若是找不到合適的處理器,那麼只能使用開銷更大的處理器。
針對上面的狀況有兩個可能的方法。第一個方法,咱們能夠指望這個狀況是暫時的,以後進程能夠被遷移回開銷更小的處理器。另外一個方法,能夠把進程的內存頁遷移到距離新處理器更近的地方。第二個方法的開銷很大,由於要把大量內存從一個地方遷移到另外一個地方,並且遷移期間舊的內存區域是不能修改的,此外還有諸多限制。操做系統應該儘可能避免使用這種策略,只有當沒辦法的時候才能採用。
爲了解決上述進程或線程遷移的狀況,默認狀況下,內存不是獨佔地分配到本地節點上的。會在全部節點上分配內存,這樣一來進程或遷移就不會產生額外的內存訪問開銷。對於較小的NUMA factor(NUMA所帶來的額外開銷)這個策略是可接受的,但仍然不是最優的(見 Section 5.4),並且它實際上下降了性能。因此,Linux容許每一個進程本身設定內存分配規則。
內核經過sys
僞文件系統提供處理器的cache信息:/sys/devices/system/cpu/cpu*/cache
在Section 6.2.1會介紹查詢各個cache尺寸的接口。在這裏咱們關注cache的拓撲結構。上述每一個目錄都有一堆index*
子目錄對應不一樣的cache,裏面有type
、level
、shared_cpu_map
這些文件。下面是Intel Core 2 QX6700的信息:
Table 5.1: sysfs Information for Core 2 CPU Caches
從上面的數據能夠看出:
下面是一個four-socket, dual-core Opteron機器的cache信息:
Table 5.2: sysfs Information for Opteron CPU Caches
從下面這個路徑能夠看處處理器的拓撲結構:/sys/devices/system/cpu/cpu*/topology
能夠看到每一個核擁有本身的L1d、L1i、L2。
下表是SMP Opteron的處理器拓撲結構:
Table 5.3: sysfs Information for Opteron CPU Topology
結合Table 5.2和Table 5.3,能夠看到沒有hyper-thread(the thread_siblings
bitmaps have one bit set)。並且其實是4個處理器(physical_package_id
0-3),每一個處理器有兩個核。
任何SMP Opteron機器都是一個NUMA機器,咱們來看看NUMA信息/sys/devices/system/node
。每一個NUMA節點都有對應的子目錄,子目錄裏有一些文件。下面是前面提到的機器的結果:
Table 5.4: sysfs
Information for Opteron Nodes
因此咱們能夠看到這個機器的全貌:
cpumap
文件裏的bit看出來。distance
文件描述了訪問每一個node的開銷。本地開銷爲10,遠程開銷都是20。(不過這裏的信息並不許確,至少有一個處理器要鏈接到南橋,因此至少有一對處理器的開銷比20大)AMD文檔裏寫了4插口機器的NUMA開銷:
Figure 5.3: Read/Write Performance with Multiple Nodes
能夠0 Hop、兩個1 Hop、2 Hop的讀寫性能差別。不過這個信息不太容易使用,Section 6.5 會將更簡單好用的方法。
咱們有可能知道memory-mapped files, Copy-On-Write (COW) pages and anonymous memory是如何分配在各個節點上的。每一個進程有本身的NUMA相關的文件/proc/<PID>/numa_maps
,看Figure 5.2:
00400000 default file=/bin/cat mapped=3 N3=3 00504000 default file=/bin/cat anon=1 dirty=1 mapped=2 N3=2 00506000 default heap anon=3 dirty=3 active=0 N3=3 38a9000000 default file=/lib64/ld-2.4.so mapped=22 mapmax=47 N1=22 38a9119000 default file=/lib64/ld-2.4.so anon=1 dirty=1 N3=1 38a911a000 default file=/lib64/ld-2.4.so anon=1 dirty=1 N3=1 38a9200000 default file=/lib64/libc-2.4.so mapped=53 mapmax=52 N1=51 N2=2 38a933f000 default file=/lib64/libc-2.4.so 38a943f000 default file=/lib64/libc-2.4.so anon=1 dirty=1 mapped=3 mapmax=32 N1=2 N3=1 38a9443000 default file=/lib64/libc-2.4.so anon=1 dirty=1 N3=1 38a9444000 default anon=4 dirty=4 active=0 N3=4 2b2bbcdce000 default anon=1 dirty=1 N3=1 2b2bbcde4000 default anon=2 dirty=2 N3=2 2b2bbcde6000 default file=/usr/lib/locale/locale-archive mapped=11 mapmax=8 N0=11 7fffedcc7000 default stack anon=2 dirty=2 N3=2
Figure 5.2: Content of /proc/PID/numa_maps
主要看N0和N3的值,這個是分配到node 0和3的頁的數量。因此能夠大概猜到進程執行在node 3的核心上。read-only mapping,好比第一個ld-2.4.so
和libc-2.4.so
和locale-archive
則分配在別的node上。
下面是真實的測試,和Figure 5.3的數據作比較,不過測試的是1 hop遠程訪問:
Figure 5.4: Operating on Remote Memory
能夠看到read老是比本地訪問慢20%,這個和Figure 5.3的數據不符合,到底爲啥只有AMD本身知道了。圖裏的幾個尖刺能夠忽略,這是由於測量多線程代碼自己的問題。
看write的比較,當working set size可以放進cache的時候,也是慢20%。當working set size超出cache的的時候,write和local訪問差很少,這是由於此時會直接訪問RAM,訪問RAM的開銷佔大頭,interconnect開銷佔小頭。