此文嚴格意義上說,我沒有很大的把握,其中關于CPU調度域的概念現在還有些混淆,但還是發出來,希望能做點鋪路的貢獻吧。
另外CPU調度域的原理我沒辦法自己寫,能力尚淺, 只能將IBM知識庫 和 另一位博主的文章摘錄如下,并添加了些幫助理解的圖片,希望能幫助到初學者。
進程綁定CPU的兩種方法對比:Scheduling Domains 和 taskset
實際上進行CPU綁定的方法共有三種: numactl 、 taskset 和 Scheduling Domains。
在本文的最后,我將這numa 和 Scheduling Domain的原理整理了下,numa 其實和Scheduling Domains要結合起來看,詳見文末。
scheduling domains方式:
從找到的一些資料中,給我的印象是它適合程序開發時,直接將程序綁定到某個核心上(即僅設置對某CPU的親和性),
而程序啟動后,在同這種方式來將進程綁定到某CPU上時,效果似乎并不太好。從下面mysql和httpd通過taskset 和
scheduling domains的對比可以有較好的示例說明。
先將我認識到的不太理想的地方列舉如下:
1. 綁定必須依賴與PID(進程號)、LWP(輕量級進程號:即線程號)
2. 將PID、LWP號綁定到tasks中后,并不一定它們就真的會馬上遷移到指定CPU上。
taskset方式:
這種方式是最常用的一種綁定CPU的方式,簡單明了。
==================================【實測部分】===========================
下面是我的測試環境說明:
1. 測試環境為:VMware中的啟動CentOS6.7, 在其中配置KVM,并啟動最小化安裝的RHEL6.4的KVM虛擬機.
2. CentOS6.7 的內存 :2G , CPU : 4
KVM虛擬機的內存:512M,CPU : 2
qemu-kvm -name rhel64-01 -m 512 -smp 2 -drive file=/images/kvm/rhel64-002.img,if=virtio,media=disk,cache=writeback,format=qcow2 -net nic,macaddr=54:52:00:00:00:03 -net tap,script=/etc/kvm/if-up,downscript=no -vnc :1 &
3. 安裝mysql: 5.1.73
httpd: 2.2.15
4. 在CentOS6.7上通過sysbench 和 ab來模擬壓力,看mysql 和 httpd在CPU0 和 CPU1之間切換的是否頻繁.
cat mytest.sh #!/bin/bash case $1 in prepare) sysbench --test=oltp --db-driver=mysql --mysql-host=192.168.137.10 --mysql-user=db1 --mysql-password=db1 --mysql-db=db1 --oltp-table-name=t1 --mysql-table-engine=MyISAM --oltp-table-size=1000000 prepare ;; run) sysbench --test=oltp --db-driver=mysql --mysql-host=192.168.137.10 --mysql-port=3306 --mysql-user=db1 --mysql-password=db1 --mysql-db=db1 --oltp-table-name=t1 --mysql-table-engine=MyISAM --mysql-engine-trx=no --oltp-skip-trx=on --oltp-table-size=1000000 --max-requests=1000 --num-threads=100 run ;; cleanup) sysbench --test=oltp --db-driver=mysql --mysql-host=192.168.137.10 --mysql-user=db1 --mysql-password=db1 --mysql-db=db1 --oltp-table-name=t1 cleanup ;; esac
cat httpdtest.sh #!/bin/bash ab -n 30000 -c 100
5. 收集mysql 和 httpd每0.5秒其線程在CPU0 和 CPU1上的變化情況的監控腳本:
[root@node2 ~]# cat m.sh #!/bin/bash main(){ monps="ps -eLo ruser,lwp,psr" rm -f httpd.0 mysql.0 httpd.1 mysql.1 total echo -e "\t\e[32mCPU 0\e[0m\t\t\e[33mCPU 1\e[0m" echo -e "\t\e[32mhttpd | mysqld\e[0m\t\e[33mhttpd | mysqld\e[0m" $monps |grep -E 'apache|mysql' |while read line; do echo $line |awk '$3==0{if($1=="apache"){printf "\t%d\n",$2 >> "httpd.0"}else{printf "%d\n",$2 >> "mysql.0"}}' echo $line |awk '$3==1{if($1=="apache"){printf "%d\n",$2 >> "httpd.1"}else{printf "%d\n",$2 >> "mysql.1"}}' echo $line >> total done maxline=`cat total |grep -E 'apache|mysql' |awk '{if($1=="apache"){a[$3]++}else{m[$3]++}}END{max=(a[0]>a[1])?((a[0]>m[0])?a[0]:m[0]):((a[1]>m[0])?a[1]:m[0]);print (max>m[1])?max:m[1]}'` for f in httpd.0 mysql.0 httpd.1 mysql.1;do lnum=`cat $f |wc -l` if [ $lnum -lt $maxline ];then l=$(($maxline-$lnum)) for i in `seq 1 1 $l`;do [ "$f" == "httpd.0" ] && echo -e "\t-" >> $f || echo '-' >> $f done fi done paste httpd.0 mysql.0 httpd.1 mysql.1 |cat -n } rm -f b while :;do sleep 0.5 main >> b done
4. 沒有配置Scheduling Domains 和 taskset前:
(1) 初始狀態,httpd 和 mysqld在cpu0 和 cpu1上都分布有線程.
注: 下面的圖顯示的都是LWP(線程號)。
(2) 啟動模擬壓測后
》先來看MySQL的對比,比較明顯(此為前0.5秒)。
…….中間省略了一些,太長了。
與上面做對比(后0.5秒):
……..
》再來看Httpd,當mysql壓力完了后;cpu0和cpu1都相對空閑, httpd遷移有所增加(前0.5秒)
結合上圖,可以看到,httpd在壓力逐漸平穩時,cpu0 和 cpu1都較忙時, 遷移比較少。
對比(后0.5秒):
從上面可以看出到,mysql 和 httpd在不做任何控制前,其線程在CPU0 和 CPU1之間的調度是非常頻繁的。
5. 配置Scheduling Domains 和 taskset分別控制mysqld 和 httpd
》使用Scheduling Domains將mysqld綁定到CPU1上。
(1) mkdir /cpusets (2) echo "cpuset /cpusets cpuset defaults 0 0" >> /etc/fstab; (3) mount -a (4) mkdir /cpusets/domain1 #注:cpuset掛載后,在其下創建任意名稱的目錄,都將被認為是一個新調度域 # 并且,kernel會在該目錄下創建調度域中的各項參數文件。 (5) echo 1 > /cpusets/domain1/cpus #cpus:用來指定該調度域中包含的CPU。 (6) echo 0 > /cpusets/domain1/mems #mems: 用來指定該調度域中能夠使用的內存段。 # 注:因為VM環境并沒有啟用numa功能,因此只有一段內存,即0段內存. # 即: 下文原理部分中,SMP結構中單內存的情況。 (7) ps -eLo ruser,pid,lwp,psr,args |grep -E 'mysql' > t root 1555 1555 0 /bin/sh /usr/bin/mysqld_safe mysql 1657 1657 1 /usr/libexec/mysqld mysql 1657 1659 0 /usr/libexec/mysqld mysql 1657 1660 0 /usr/libexec/mysqld mysql 1657 1661 0 /usr/libexec/mysqld mysql 1657 1662 1 /usr/libexec/mysqld mysql 1657 1670 1 /usr/libexec/mysqld mysql 1657 1671 0 /usr/libexec/mysqld mysql 1657 1672 0 /usr/libexec/mysqld mysql 1657 1673 1 /usr/libexec/mysqld mysql 1657 1674 0 /usr/libexec/mysqld (8) awk '{print $3}' t >> /cpusets/domain1/tasks #指定將那個進程綁定到該域的任務列表中。
(9) 綁定后,查看線程當前所在的CPU,發現還有線程在CPU0上,不知道是否做錯了?
ps -eLo ruser,pid,lwp,psr |grep -E 'mysql|1555' root 1555 1555 0 mysql 1657 1657 1 mysql 1657 1659 0 mysql 1657 1660 0 mysql 1657 1661 0 mysql 1657 1662 1 mysql 1657 1670 1 mysql 1657 1671 1 mysql 1657 1672 1 mysql 1657 1673 1 mysql 1657 1674 1
》使用taskset將httpd綁定到CPU0上。
taskset -c 0 services httpd start [root@rhel64 ~]# ps -eLo ruser,pid,lwp,psr |grep 'apache' apache 30473 30473 0 apache 30477 30477 0 apache 30479 30479 0 apache 30721 30721 0 apache 31097 31097 0 ..... 全部都已經綁定到CPU0上了。 apache 31636 31636 0 apache 31660 31660 0 apache 31667 31667 0
6. 在CentOS6.7上模擬壓力后,結果如下:
》開始前
》運行中
》httpd 和 mysqld的壓力基本都上來后.
從上面這些圖中可以看到,taskset綁定的效果 十分明顯,而scheduling Domains的效果就有點不太理想。
==================================【理論部分】===========================
Scheduling Domains 引入的背景[http://www.ibm.com/developerworks/cn/linux/l-cn-schldom/]
Scheduling Domains 是現代硬件技術尤其是多 CPU 多核技術發展的產物。
現在,一個復雜的高端系統由上到下可以這樣構成:
1. 它是一個 NUMA 架構的系統,系統中的每個 Node 訪問系統中不同區域的內存有不同的速度。
2. 同時它又是一個 SMP 系統。由多個物理 CPU(Physical Package) 構成。
這些物理 CPU 共享系統中所有的內存。但都有自己獨立的 Cache 。
3. 每個物理 CPU 又由多個核 (Core) 構成,即 Multi-core 技術或者叫 Chip-level Multi processor(CMP) 。
這些核都被集成在一塊 die 里面。一般有自己獨立的 L1 Cache,但可能共享 L2 Cache 。
4. 每個核中又通過 SMT 之類的技術實現多個硬件線程,或者叫 Virtual CPU( 比如 Intel 的 Hyper-threading 技術 ) 。
這些硬件線程,邏輯上看是就是一個 CPU 。它們之間幾乎所有的東西都共享, 包括 L1 Cache,
甚至是邏輯運算單元 (ALU) 以及 Power 。
注:
SMT(Simultaneous Multithreading:同步多線程):它的基本結構實現原理:在一個時鐘周期內向功能部件發送多個線程的指令,以提高線程對功能部件的利用率; Intel的研究人員基于SMT技術設計了超線程(Hyper-Threading)技術,超線程技術使一顆物理CPU,從程序運行角度上看,可呈現多個邏輯CPU的效果.
MCH(Memory Controller Hub)相當于北橋芯片。INTEL從815時就開始放棄了南北橋的說法 MCH 是內存控制器中心 ,負責連接CPU/PCI/AGP/PCIEXPRESS總線各內存,還有ICH(I/O controller hub)即輸入/輸出控制中心,相當于南橋芯片,負責IDE和I/O設備等。
內存控制器(Integrated Memory Controller)簡稱IMC,它是MCH中用來訪問內存的組件,它顯著的降低了內存延遲,提升性能,配合QPI(快速通道技術)使CPU讀寫巨量數據到內存的速度大大提高.
IOH(Input Output Hub)也就是傳統意義上部分南橋的功能,通過QPI總線與CPU相連,下方使用DMI總線連接南橋ICH芯片,主要負責I/O總線的傳輸,它同時提供了很多PCI-E 2.0總線連接作為標準的I/O接口。因為Intel的CPU已經把內存控制器(MCH)總線集成進了CPU,也就是說把傳統意義上的北橋做進了CPU里。并不是MCH變成了IOH,而是MCH已經不需要存在在主板上了。IOH的功能已經逐漸整合到CPU中。
在上述系統中,最小的執行單元是邏輯 CPU,進程的調度執行也是相對于邏輯 CPU 的。
在這樣復雜的系統,調度器要解決的一個首要問題就是如何發揮這么多 CPU 的性能,使得負載均衡,
不存某些 CPU 一直很忙,進程在排隊等待運行,而某些 CPU 卻是處于空閑狀態。
但是在這些 CPU 之間進行 Load Balance 是有代價的,比如對處于兩個不同物理 CPU 的進程之間進行負載平衡的話,將會使得 Cache 失效, 造成效率的下降,而且過多的 Load Balance 會大量占用 CPU 資源。
還有一個要考慮的就是功耗 (Power) 的問題。一個物理 CPU 中的兩個 Virtual CPU 各執行一個進程,顯然比兩個物理 CPU 中的 Virtual CPU 各執行一個進程節省功耗。因為硬件上可以實現一個物理 CPU 不用時關掉它以節省功耗。
為了解決上述的這些問題,內核開發人員 Nick Piggin 等人在 Linux 2.6 中引入基于 Scheduling Domains 的解決方案。
注:從其他一些文章中看到 Numa調度域是塔尖,上面的Numa-domain是Numa的節點域,它們包含在Numa調用域中,
這是超大型CPU陣列中才可能有的,每一層調度的成本逐級增加,也逐級增大調度的阻力,見下面。
(1) 當系統啟動時,內核會分別把每個核的兩個邏輯CPU 放入一個 Scheduling Domain,這個級別的domain叫做cpu_domain。
其中每個domain包括兩個 CPU groups,每個 CPU group 只有一個邏輯 CPU 。
(2) 同時每個物理 CPU 的兩個核被放入一個高一級的 Scheduling Domain 。這個 domain 命名為core_domain 。
其中每個 domain 包括兩個 CPU groups,每個 CPU group 有兩個邏輯 CPU 。
(3) 再往上的話依次還有phys_domain(物理 CPU 放入的 domain) ;
(4) numa_domain(NUMA 中的 16 個 nodes 放入的 domain) ;
allnode_domain( 所有 NUMA 中的 node 放入的 domain) ,從而將所有 CPU 組織成一個基于 domain 的層次結構。
注:numa_domain中放入16個nodes,這16個nodes指什么?【沒有完全理解.】
負載均衡算法分析(動態表現)[http://blog.csdn.net/dog250/article/details/6538240]
遷移一個進程的代價就是削弱cache的作用。 因此,只要在擁有cache的處理器之間遷移進程,勢必會付出這個代價,因此在設計中必然需要一種“阻力”來盡量不做進程遷移,除非萬不得已!這種“阻力”就是負載均衡原則2中的“加權”系數。
1).歷史負載值的影響
為防止處理器負載曲線的上下顛簸,用歷史值來加權當前值是一個不錯的方式 ,也就是說,所謂的負載曲線不再基于時間點 ,而是基于時間段。 然而歷史負載值對總負載的影響肯定沒有當前的負載值對總負載的影響大,一個時間點的負載值隨著時間的流逝,對負載均衡時總負載的計算的影響應該逐漸減小。因此Linux的負載均衡器設計了一個公式專門用于負載均衡過程中對cpu總負載的計算。該公式如下:
total_load=(previous_load*(delta-1)+nowa_load)/delta
其中delta是一個可變的系數,在linux 2.6.18中設置了3個delta,分別為1,2,4,當然還可以更多,比如高一些版本的內核中delta的取值有CPU_LOAD_IDX_MAX種,CPU_LOAD_IDX_MAX由宏來定義。比如當delta為2的時候,上述公式成為:
total_load=(previous_load+nowa_load)/2
相當于歷史值占據整個load值一半,而當前值占據另一半。
2).波峰/波谷的平滑化
讓歷史值參與計算總負載解決了同一條負載曲線顛簸的問題,但是在負載均衡時是比較兩條負載曲線同一時間點上的值 ,當二者相差大于一個閥值時,實施進程遷移。 為了做到“盡量不做進程遷移”這個原則,必須將兩條負載曲線的波峰和波谷平滑掉。 如果進程遷移源cpu的負載曲線此時正好在波峰,目的cpu的負載曲線此時正好在波谷,此時就需要將波峰和波谷削平,讓源cpu的負載下降a,而目的cpu的負載上升b,這樣它們之間的負載差就會減少a+b,這個“阻力”足以阻止很多的進程遷移操作。
3).負載曲線平滑操作的基準
負載均衡平滑操作時需要兩個值,即上述的a和b,這兩個值決定了削平波峰/波谷的幅度,幅度越大,阻礙負載均衡的“力度”也就越大,反之“力度”也就越小。根據參與負載均衡的cpu的層次級別的不同,這種幅度應該不同,幸運的是,可以根據調整“負載均衡過程中對cpu總負載的計算公式”中的delta來影響幅度的大小, 這樣,1)和2)就在這點上獲得了統一。對于目的cpu,取計算得到的total_load和nowa_load之間的最大值,而對于源cpu,則取二者最小值。可以看出,在公式中,如果delta等于1,則不執行削波峰/波谷操作,這適用于smt的情況,delta越大,歷史負載值的影響也就越大,削波峰/波谷后的源cpu負載曲線和目的cpu負載曲線的差值曲線越趨于平滑,這樣就越能阻止負載均衡操作(差分算法….)。
4).自下而上的遍歷方式
Linux在基于調度域進行負載均衡的時候采用的是自下而上的遍歷方式,這樣就優先在對cache影響最小的cpu之間進行負載均衡,同時這種均衡操作會增加本cpu的負載,反過來在比較高的調度域級別上有力的阻止了對cache影響很大的cpu之間的負載均衡。 我們知道,調度域是從對cache影響最小的最底層向高層構建的。
5).結論
隨著cpu級別的提高,由于負載均衡對cache利用率的影響逐漸增大,“阻力”也應該逐漸加大,因此負載均衡對應調度域使用的delta也應該增加。 算法的根本要點是什么呢?畫幅圖就一目了然了,delta越大,負載值受歷史值的影響越大,因此按照公式所示,只有持續單調遞增 的cpu負載,在源cpu選擇時才會被選中,偶然一次的高負載并不足以引起其上的進程遷移至別處,相應的,只有負載持續單調遞減 ,才會引起其它cpu上的進程遷移至此,這正體現了負載以一個時間段而不是一個時間點為統計周期! 而級別越高的cpu間的進程遷移,需要的“阻力”越大,因此就越受歷史值的影響,因為只要歷史中有一次負載很小,就會很明顯的反應在當前,同樣的道理,歷史中有一次的負載很大,也很容易反映在當前;反之,所需“阻力”越小,就越容易受當前負載值的影響,極端的情況下,超線程的不同邏輯cpu之間的負載計算公式中delta為1,因此它們的負載計算結果完全就是該cpu的當前負載!
結論有三:
5.1).通過“負載均衡過程中對cpu總負載的計算公式”平滑了單獨cpu的負載曲線,使之不受突變的影響,平滑程度根據delta微調
5.2).通過“削掉波峰/波谷”平滑了源cpu和目的cpu負載曲線在負載均衡這個時間點的差值,盡可能阻止進程遷移,阻止程度根據delta微調
5.3).執行負載均衡的過程中,一輪負載均衡在每一層的效果需要隨著級別的升高而降低,這通過自下而上的遍歷方式來完成
負載均衡的配置
一.概述
值得注意的是,Linux所實現的調度域和調度組僅僅描述了一個cpu的靜態拓撲和一組默認的配置, 這組默認的配置生成的原則就是在第一部分中描述的各種情況的基礎上在負載均衡和cache利用率之間產生最小的對抗 , Linux并沒有將這些配置定死,實際上Linux的負載均衡策略是可以動態配置的。
由于負載均衡實現的時候,對調度域數據結構對象使用了淺拷貝 ,因此對于每一個最小級別的cpu,都有自己的可配置參數,而對于所有屬于同一調度域的所有cpu而言,它們有擁有共享的調度組,這些共享的信息在調度域數據結構中用指針實現,因此對于調度域參數而言,每個cpu是可以單獨配置的。每個cpu都可配置使得可以根據底層級別cpu上的負載情況(負載只能在最底層級別的cpu上)進行靈活的參數配置,還可以完美支持虛擬化和組調度。
二.配置方法
目錄/proc/sys/kernel/sched_domain 下有所有的最底層級別的cpu目錄,比如你的機器上有4個物理cpu,每個物理cpu有2個核心,每個核心都開啟了超線程,則總共的cpu數量是4*2*2=16.
注:這里我并沒有理解cpuset 與 sched_domain的聯系,若有知情者希望分享。
busy_factor #less balancing by factor if busy
busy_idx #忙均衡的cpu_load索引
cache_nice_tries #Leave cache hot tasks for # tries
idle_idx #空閑均衡的cpu_load索引
imbalance_pct #判斷該調度域是否已經均衡的一個基準值。
min_interval #最小均衡間隔
max_interval #最大均衡間隔
newidle_idx #馬上就要進入idle的cpu為了盡量不進入idle而進行負載均衡時的cpu_load索引
wake_idx #喚醒進程時將進程拉至本cpu的作用力,也是一個cpu_load索引,該索引也即本地喚醒力,對于支持多隊列的網卡有很大的性能增強作用。
cache_hot_time #A進程從上次調離CPU到本次即將調入CPU的時間為b,若b小于cache_hot_time,則認為A上次的緩存信息失效的可能性最小,因此調度到當前CPU上最佳.
per_cpu_gain # CPU % gained by adding domain cpus
=======================================================
taskset : 綁定進程到某個CPU上。
Mask
# Mask:為CPU親和性的掩碼數字,轉為二進制后,從低到高位,分別代表cpu0,cpu1,…,
# 位置為1則允許該進程在該CPU上運行。
# 如: 我的測試宿主機是4核CPU, 現在要將VM的vCPU綁定到cpu3上,則掩碼可寫為: 0x8,
# 二進制為1000, 按從低到高位,則: cpu0,cpu1,cpu2都不允許3623線程運行,僅cpu3允許.
-c 可不使用掩碼表示法,直接指定CPU編號:
如:-c 0-2,7 : 表示0,1,2,7
taskset -p 0x8 3623
taskset -p -c 0-2,7 101 //將PID為101的進程綁定到0,1,2,7上。 -p :指定綁定的PID
taskset -c 0 services httpd start
===========================================================
SMP(對稱多處理器)
即:一個塊主板上有多個CPU插槽(Socket),每個socket可插一顆CPU。
SMP下多個CPU共享同一個內存,非常容易造成資源爭用,并且若一個顆CPU為多核心的,則它還可能會存在3級緩存的資源爭用。
CPU在訪問內存時,它是需要花三個時鐘周期的,
第一周期:CPU向內存控制器(它主要用來管理內存地址分配)發生尋址指令,由內存控制器負責返回內存地址是多少。
第二周期:CPU找到該內存地址,并根據讀 或 寫,來向該內存區域施加訪問機制(可簡單理解 讀鎖 或 寫鎖)。
第三周期:CPU完成讀 或 寫的操作。
NUMA(非一致性內存訪問)
對稱多處理器上采用共享內存的方式,效率會因資源爭用而降低;若為每個CPU都分配自己專屬的內存控制器 和 內存區域,并且,內存控制器與CPU封裝在一起,這樣不僅資源爭用可以降低,并且也可提高效率。這就是下面要說的NUMA的原理:
假如當前有2顆CPU,CPU0 和 CPU1,在系統初始時有200個進程,分別被平均分配給CPU0和CPU1,運行一段時間后,CPU0上只剩下10個活動進程,而CPU1上還有90個活動進程,這嚴重打破了運行的平衡,這時系統會自動判斷并進行rebalancing(重新平衡),這時CPU0上會被掉過來40個進程,這時CPU0和CPU1又進入平衡,但平衡只能將CPU上寄存器中數據搬移過去,但無法將內存的數據搬移到CPU0的專屬內存區域(注:實際這是沒有必要的),這時CPU0在運行這40個進程時,就需要跨socket才能訪問到CPU1內的內存控制器,并完成CPU1的專屬內存訪問。而這種訪問次要內存區域的方式就是NUMA.
補充: 【http://blog.csdn.net/chrysanthemumcao/article/details/9236321】
從系統架構來說,目前的主流企業服務器基本可以分為三類:SMP (Symmetric Multi Processing,對稱多處理架構),NUMA (Non-Uniform Memory Access,非一致存儲訪問架構),和MPP (Massive Parallel Processing,海量并行處理架構)。三種架構各有特點,本文將重點聊聊NUMA。
為了了解NUMA,我這里就介紹一下NUMA與其他兩種Non-NUMA的主要區別。
1.SMP(Symmetric Multi Processing)
SMP是非常常見的一種架構。在SMP模式下,多個處理器均對稱的連接在系統內存上,所有處理器都以平等的代價訪問系統內存。它的優點是對內存的訪問是平等、一致的;缺點是因為大家都是一致的,在傳統的 SMP 系統中,所有處理器都共享系統總線,因此當處理器的數目增多時,系統總線的競爭沖突迅速加大,系統總線成為了性能瓶頸,所以目前 SMP 系統的處理器數目一般只有數十個,可擴展性受到很大限制。
2.MPP (Massive Parallel Processing)
MPP則是邏輯上將整個系統劃分為多個節點,每個節點的處理器只可以訪問本身的本地資源,是完全無共享的架構。節點之間的數據交換需要軟件實施。它的優點是可擴展性非常好;缺點是彼此數據交換困難,需要控制軟件的大量工作來實現通訊以及任務的分配、調度,對于一般的企業應用而言過于復雜,效率不高。
3.NUMA(Non-Uniform Memory Access)
NUMA架構則在某種意義上是綜合了SMP和MPP的特點:邏輯上整個系統也是分為多個節點,每個節點可以訪問本地內存資源,也可以訪問遠程內存資源,但訪問本地內存資源遠遠快于遠程內存資源。它的優點是兼顧了SMP和MPP的特點, 易于管理,可擴充性好;缺點是訪問遠程內存資源的所需時間非常的大。
在實際系統中使用比較廣的是SMP和NUMA架構。像傳統的英特爾IA架構就是SMP,而很多大型機采用了NUMA架構。
> 同步多線程技術(SMT)
同步多線程技術使得每個核心可以同時執行2個線程,所以對于4核的CPU來說,就可以在每個處理器芯片
上達到最大8個邏輯處理器。
前面介紹了NUMA的很牛的架構,那目前系統層面上,軟件對NUMA的支持怎么樣呢?
對于NUMA架構而言,經過了幾十年的發展,目前的軟件支持棧已經非常完備,從底層的操作系統,
到之上的數據庫、應用服務器,基本所有主流的產品均以為NUMA提供了充分的支持。
? 操作系統(Operating System)
目前,Windows Server 2003 和Windows XP 64-bit Edition, Windows XP等都是NUMA aware的,
而Windows Vista則有了對Numa調度的支持。所有使用2.6版本以上kernel的Linux操作系統都能夠支持NUMA。
而Solaris,HP-Unix等UNIX操作系統也是充分支持NUMA架構的。
? 數據庫(Database)
對于數據庫產品來說,Oracle從8i開始支持NUMA,而之后的Oracle9i,Oracle10g,Oracle11g都能夠
支持NUMA。SQL Server 2005 和SQL Server 2008均有效的提供了對NUMA的支持。
? 中間件服務器(Middleware)
目前業界典型的受控程序主要是Java應用和.Net應用。由于內存分配,線程調度對于應用而言是透明的,
完全是由虛擬機來處理。因此它們在NUMA環境下的性能表現主要取決于虛擬機的實現是否能充分利用到底層
操作系統對NUMA的支持。
綜上所述,目前的軟件棧對NUMA架構均已經作了充分的支持。那么應用軟件如何支持NUMA架構呢?
在傳統SMP系統上,所有CPU都以同樣的方式通過一個共享內存控制器來訪問內存,各CPU之間也是通過
它來進行交流,所以很容易造成擁堵。而一個內存控制器所能夠管理的內存數量也是非常有限的。此外,通過唯一
的hub(原意:集線器,此意:共享方式)訪問內存造成的延遲也是非常高的。
在NUMA結構下,每個計算機不再只有唯一的內存控制器,而是把整個系統分成多個節點。每個節點分別有自己
的處理器和內存。系統中所有的節點都通過全互聯的方式連接。所以,每當在系統中增加新的節點,系統所能夠支持
的內存和帶寬都會增加,具有非常好的擴展性。
NUMA的內存組織
在NUMA系統中,每個CPU可以訪問兩種內存:本地內存(Local Memory)和遠端內存(Remote Memory)。
和CPU在同一個節點的內存稱為本地內存,訪問延遲非常低。不和CPU在同一節點上的內存叫做遠端內存,CPU需要
通過節點間互聯的方式訪問,所以訪問延遲要比訪問本地內存長。
從軟件的角度來看,遠端內存和本地內存是以同樣的方式訪問的。理論上講,NUMA系統可以被軟件視為與
SMP同樣的系統,不區分本地和遠端內存。但是如果追求更好的性能,這個區別還是需要被考慮的。
經實驗,對于常規的內存操作,如清空(Memset),塊復制(Memcpy),流讀寫(Stream),指針追溯
(Pointer Chase)等操作來說,本地內存的訪問速度要遠遠優于遠端內存。由于 NUMA 同時使用本地內存和遠端內存,
因此,訪問某些內存區域的時間會比訪問其他內存區域的要長。
本地和遠端內存通常是相對于進程而言的,如上圖,A和B分別運行在節點0上的Core0和Core1上,它們訪問的內存
就是本地內存,而C需要的內存比較多,假設節點1端的本地內存不夠用,這時設置C進程可以使用節點0端的內存,這時
C訪問節點0端的內存就稱為遠端內存。訪問遠端內存的開銷與訪問本地內存的開銷比率稱為 NUMA 比率。如果
NUMA 比率為 1,則它是對稱多處理 (SMP)。比率越高,訪問其他節點內存的開銷就越大。不支持 NUMA 的 應用程序
有時在 NUMA 硬件上的執行效果非常差。由于訪問本地內存和遠端內存的開銷是有區別的,所以在NUMA模式下,
如果每個線程更多的是訪問本地內存,那么性能相比而言會有一定提升。
NUMA策略,即如何更好的利用NUMA來給咱們干活:
為描述在NUMA架構下針對內存訪問的優化,我們可以引入NUMA策略的概念。
NUMA策略(NUMA Policy)即是指在多個節點上合理的進行內存分配的機制。對于不同軟件設計要求,
策略的目標可能會不同:有一些設計可能強調低延遲訪問,另一些則可能更加看重內存的訪問帶寬。
對于強調低延遲訪問的設計,基本的分配方式就是盡量在線程的本地內存上為其進行分配, 并盡量讓
線程保持在該節點上。這稱為線程的節點親和性(Node affinity)。這樣既充分利用了本地內存的低延遲,
同時也能有效降低節點間的通信負擔。
NUMA架構的一個優勢是,即便是在擁有大量CPU的大規模系統中,我們也可以保證局部內存訪問的低延遲。
通常來講,CPU的處理速度是遠大于內存的存取速度的。在讀寫內存時,CPU常常需要花大量的時鐘周期來等待。
降低內存訪問的延遲因而能夠有效的提升軟件性能。
另外,為SMP設計的操作系統通常會有緩存親和性(Cache Affinity) 的優化措施。
緩存親和性機制即:讓數據盡量長時間的保留在某一CPU的緩存中,而非來回在多個CPU的緩存里換來換去。
簡單說:A在CPU0的core1上運行,時間片用完了,被調入內存睡眠,當A在次被導入CPU上運行時,緩存親和性就
會盡可能將A還調度到CPU0的core1上運行,以便A在core1上執行時,可以利用上次緩存在core1的寄存器中的數據.
這一機制顯然是和NUMA系統盡量利用本地內存的策略是一致的,有利于面向SMP系統的程序向NUMA架構移植。
緩存親和性機制 與 NUMA系統的節點親和性是有區別的:
(1) 同一個節點間多個核心或每核心的線程間遷移并不影響該線程的節點親和性;
(2) 當線程被迫遷移到其他節點時,他所擁有的內存是不跟著遷移且仍留在原處。此時,本地內存就變成遠端內存了,
對它的訪問既慢又占用節點間通信帶寬。相對的,線程在遷移之后能夠以較小的代價迅速建立起新的緩存,
并繼續在新CPU上體現緩存的親和優勢。 因此,NUMA系統對于節點親和性的依賴更大。
(3) OS調度器默認是將CPU利用率作為優化依據,而非節點親和性,因為頻繁訪問遠端內存的性能開銷相對于讓
CPU空閑的性能開銷小.若特定應用系統的性能受內存訪問的影響遠大于CPU的利用率,此時程序員或者管理員
則可采用特別的NUMA策略來強調節點的親和性,從而提升性能。
另外, 盡管大部分應用會因為優化響應時間而收益,還有一部分應用則對內存帶寬比較敏感。為了提升內存帶寬,NUMA架構下的多個內存控制器可以并行使用。這類似于RAID陣列通過并行處理磁盤IO來提升讀寫性能。通過適當的軟件或者硬件機制,NUMA架構可以使內存控制單元在各個內存控制器上交替的分配內存。這意味著分配得到的連續內存頁面會水平地分布到各個節點上。當應用程序對內存進行流式讀寫時,各個內存控制器的帶寬就相當于累加了。此機制獲得性能提升決定于NUMA架構的實現。對于遠端內存訪問延遲嚴重的架構,該提升往往會比較明顯。在一些NUMA系統中,系統硬件本身提供了節點交織分配機制;而在沒有硬件提供節點交織的系統中,可由操作系統來實現該機制。
NUMA相關命令:【在RHEL6.4以后,才有這些命令】
numastat : 是獲取NUMA內存訪問統計信息的命令行工具。對于系統中的每個節點,內核維護了一些有關NUMA分配狀態的統計數據。numastat命令會基于節點對內存的申請,分配,轉移,失敗等等做出統計,也會報告NUMA策略的執行狀況。這些信息對于測試NUMA策略的有效性是非常有用的。
numa_hit : CPU訪問專屬內存的命中的次數。
numa_miss : CPU訪問專屬內存時未命中的次數。
numa_foreign : CPU的專屬內存被外部CPU使用次數(即: CPU0的專屬內存,被CPU1使用的次數).
選項:
-p <PID> or <Pattern> : 查看某個PID進程是否跨越了多個專屬內存區域。
注:若一個進程跨越了多個專屬內存區域,最好是將其綁定到一顆CPU上。
-s <node#> : 顯示單個節點和匯總信息。【注:它主要用來排序】
numactl : 是設定進程NUMA策略的命令行工具。對于那些無法修改和重新編譯的程序,
它可以進行非常有效的策略設定。Numactl使管理員可以通過簡單的命令行調用來設定進程的策略,
并可以集成到管理腳本中?!具@是臨時生效的CPU綁定方式。】
Numactl的主要功能包括:
1. 設定進程的內存分配基本策略
2. 限定內存分配范圍,如某一特定節點或部分節點集合
3. 對進程進行節點或節點集合的綁定
4. 修改命名共享內存,tmpfs或hugetblfs等的內存策略
5. 獲取當前策略信息及狀態
6. 獲取NUMA硬件拓撲
#下面是使用numactl設定進程策略的實例: # 在節點0上的CPU運行名為program的程序,并且只在節點0,1上分配內存。 # --cpunodebind=<node#> or -N <node#> : 將某個command與CPU中指定node進行綁定。 # --membind=<node#>[,<nodes#>,...] or -m <nodes#> :僅允許給該App分配指定節點的內存. # 注: 若每個節點上有多個邏輯CPU的系統上,其邏輯CPU編號的定義順序可能會不同。 numactl --cpunodebind=0 --membind=0,1 program #下面是使用numactl更改共享內存段的分配策略的實例: # 對命名共享內存interleaved進行設置,其策略為全節點交織分配,大小為1G。 # --interleave=[all |<nodes#>] : 交叉在指定節點上分配內存 或 在全部節點上交叉分配內存。 numactl --length=1G --file=/dev/shm/interleaved --interleave=all 其它參數: --show : 查看Numa當前的策略信息。 --physcpubind=<cpu#> or -C <cpu#> : 將進程和物理CPU完成綁定。 numad # 它是一個用戶空間進程,可提供策略,可自動根據觀察每個CPU節點上進程的運行狀況, # 來自動將進程關聯到指定CPU上,并將該CPU綁定到指定node上,它實現了自我監控 和 # 優化管理。是否要使用它可以根據numastat中的numa_miss來觀察。 # 它可以只監控指定的進程,并且可指定多久(最長 和 最短)觀察一次 和 觀察級別。
原創文章,作者:Wn1m,如若轉載,請注明出處:http://www.www58058.com/15797