接上篇《NFV關(guān)鍵技術(shù):DPDK技術(shù)棧在網(wǎng)絡(luò)云中的最佳實踐01》
4、DDIO(Data Direct I/O)數(shù)據(jù)直連技術(shù)
如今,隨著大數(shù)據(jù)和云計算的爆炸式增長,寬帶的普及以及個人終端網(wǎng)絡(luò)數(shù)據(jù)的日益提高,對運營商服務(wù)節(jié)點和數(shù)據(jù)中心的數(shù)據(jù)交換能力和網(wǎng)絡(luò)帶寬提出了更高的要求。并且,數(shù)據(jù)中心本身對虛擬化功能的需求也增加了更多的網(wǎng)絡(luò)帶寬需求。為此,英特爾公司提出了Intel® DDIO(Data Direct I/O)的技術(shù)。該技術(shù)的主要目的就是讓服務(wù)器能更快處理網(wǎng)絡(luò)接口的數(shù)據(jù),提高系統(tǒng)整體的吞吐率,降低延遲,同時減少能源的消耗。
當(dāng)一個網(wǎng)絡(luò)報文送到服務(wù)器的網(wǎng)卡時,網(wǎng)卡通過外部總線(比如PCI總線)把數(shù)據(jù)和報文描述符送到內(nèi)存。接著,CPU從內(nèi)存讀取數(shù)據(jù)到Cache進而到寄存器。進行處理之后,再寫回到Cache,并最終送到內(nèi)存中。最后,網(wǎng)卡讀取內(nèi)存數(shù)據(jù),經(jīng)過外部總線送到網(wǎng)卡內(nèi)部,最終通過網(wǎng)絡(luò)接口發(fā)送出去??梢钥闯觯瑢τ谝粋€數(shù)據(jù)報文,CPU和網(wǎng)卡需要多次訪問內(nèi)存。而內(nèi)存相對CPU來講是一個非常慢速的部件。CPU需要等待數(shù)百個周期才能拿到數(shù)據(jù),在這過程中,CPU什么也做不了。
DDIO技術(shù)思想就是使外部網(wǎng)卡和CPU通過LLC Cache直接交換數(shù)據(jù),繞過了內(nèi)存這個相對慢速的部件。這樣,就增加了CPU處理網(wǎng)絡(luò)報文的速度(減少了CPU和網(wǎng)卡等待內(nèi)存的時間),減小了網(wǎng)絡(luò)報文在服務(wù)器端的處理延遲。這樣做也帶來了一個問題,就是網(wǎng)絡(luò)報文直接存儲在LLC Cache中,對這一級cache的容量有很大需求。因此,在英特爾的E5處理器系列產(chǎn)品中,把LLC Cache的容量提高到了20MB。DDIO處理網(wǎng)絡(luò)報文流程示意圖如下:
為了發(fā)送一個數(shù)據(jù)報文到網(wǎng)絡(luò)上去,首先是運行在CPU上的軟件分配了一段內(nèi)存,然后把這段內(nèi)存讀取到CPU內(nèi)部,更新數(shù)據(jù),并且填充相應(yīng)的報文描述符(網(wǎng)卡會通過讀取描述符了解報文的相應(yīng)信息),然后寫回到內(nèi)存中,通知網(wǎng)卡,最終網(wǎng)卡把數(shù)據(jù)讀回到內(nèi)部,并且發(fā)送到網(wǎng)絡(luò)上去。但是,沒有DDIO技術(shù)和有DDIO技術(shù)條件的處理方式是不同的。
a) 沒有DDIO時,如上圖所示:
1)CPU更新報文和控制結(jié)構(gòu)體。由于分配的緩沖區(qū)在內(nèi)存中,因此會觸發(fā)一次Cache不命中,CPU把內(nèi)存讀取到Cache中,然后更新控制結(jié)構(gòu)體和報文信息。之后通知NIC來讀取報文。
2)NIC收到有報文需要傳遞到網(wǎng)絡(luò)上的通知后,讀取控制結(jié)構(gòu)體進而知道去內(nèi)存中讀取報文信息。
3)由于之前CPU剛把該緩沖區(qū)從內(nèi)存讀到Cache中并且做了更新,很有可能Cache還沒有來得及把更新的內(nèi)容寫回到內(nèi)存中(回寫機制)。因此,當(dāng)NIC發(fā)起一個對內(nèi)存的讀請求時,很有可能這個請求會發(fā)送到Cache系統(tǒng)中,Cache系統(tǒng)會把數(shù)據(jù)寫回到內(nèi)存中。
4)嘴周,內(nèi)存控制器再把數(shù)據(jù)寫到PCI總線上去,NIC從PCI總線上讀取數(shù)據(jù)。
b) 有DDIO時,如上圖所示:
1)CPU更新報文和控制結(jié)構(gòu)體。這個步驟和沒有DDIO的技術(shù)類似,但是由于DDIO的引入,處理器會開始就把內(nèi)存中的緩沖區(qū)和控制結(jié)構(gòu)體預(yù)取到Cache,因此減少了內(nèi)存讀的時間。
2)NIC收到有報文需要傳遞到網(wǎng)絡(luò)上的通知后,通過PCI總線去讀取控制結(jié)構(gòu)體和報文。利用DDIO技術(shù),I/O訪問可以直接將Cache的內(nèi)容送到PCI總線上。這樣,就減少了Cache寫回時等待的時間。
由此可以看出,由于DDIO技術(shù)的引入,網(wǎng)卡的讀操作減少了訪問內(nèi)存的次數(shù),因而提高了訪問效率,減少了報文轉(zhuǎn)發(fā)的延遲。在理想狀況下,NIC和CPU無需訪問內(nèi)存,直接通過訪問Cache就可以完成更新數(shù)據(jù),把數(shù)據(jù)送到NIC內(nèi)部,進而送到網(wǎng)絡(luò)上的所有操作。
有網(wǎng)絡(luò)報文需要送到系統(tǒng)內(nèi)部進行處理,其過程一般是NIC從網(wǎng)絡(luò)上收到報文后,通過PCI總線把報文和相應(yīng)的控制結(jié)構(gòu)體送到預(yù)先分配的內(nèi)存,然后通知相應(yīng)的驅(qū)動程序或者軟件來處理。和之前網(wǎng)卡的讀數(shù)據(jù)操作類似,有DDIO技術(shù)和沒有DDIO技術(shù)的處理也是不一樣的。
a) 沒有DDIO時,如上圖所示:
1)報文和控制結(jié)構(gòu)體通過PCI總線送到指定的內(nèi)存中。如果該內(nèi)存恰好緩存在Cache中(有可能之前CPU有對該內(nèi)存進行過讀寫操作),則需要等待Cache把內(nèi)容先寫回到內(nèi)存中,然后才能把報文和控制結(jié)構(gòu)體寫到內(nèi)存中。
2)運行在CPU上的驅(qū)動程序或者軟件得到通知收到新報文,去內(nèi)存中讀取控制結(jié)構(gòu)體和相應(yīng)的報文,Cache不命中。之所以Cache一定不會命中,是因為即使該內(nèi)存地址在Cache中,在步驟1中也被強制寫回到內(nèi)存中。因此,只能從內(nèi)存中讀取控制結(jié)構(gòu)體和報文。
b) 有DDIO時,如上圖所示:
1)這時,報文和控制結(jié)構(gòu)體通過PCI總線直接送到Cache中。這時有兩種情形:場景一就是如果該內(nèi)存恰好緩存在Cache中(有可能之前處理器有對該內(nèi)存進行過讀寫操作),則直接在Cache中更新內(nèi)容,覆蓋原有內(nèi)容。場景二就是如果該內(nèi)存沒有緩存在Cache中,則在最后一級Cache中分配一塊區(qū)域,并相應(yīng)更新Cache表,表明該內(nèi)容是對應(yīng)于內(nèi)存中的某個地址的。
2)運行在CPU上的驅(qū)動或者軟件被通知到有報文到達,其產(chǎn)生一個內(nèi)存讀操作,由于該內(nèi)容已經(jīng)在Cache中,因此直接從Cache中讀。
由此可以看出,DDIO技術(shù)在CPU和外設(shè)之間交換數(shù)據(jù)時,減少了CPU和外設(shè)訪問內(nèi)存的次數(shù),也減少了Cache寫回的等待,提高了系統(tǒng)的吞吐率和數(shù)據(jù)的交換延遲。
NUMA系統(tǒng)
從系統(tǒng)架構(gòu)來看,目前的商用服務(wù)器大體可以分為三類,即對稱多處理器結(jié)構(gòu)(SMP :Symmetric Multi-Processor),非一致存儲訪問結(jié)構(gòu)(NUMA :Non-Uniform Memory Access),以及海量并行處理結(jié)構(gòu)(MPP :Massive Parallel Processing)。它們的特征如下:
SMP (Symmetric Multi Processing),對稱多處理系統(tǒng)內(nèi)有許多緊耦合多處理器,在這樣的系統(tǒng)中,所有的CPU共享全部資源,如總線,內(nèi)存和I/O系統(tǒng)等,操作系統(tǒng)或管理數(shù)據(jù)庫的復(fù)本只有一個,這種系統(tǒng)有一個最大的特點就是共享所有資源。多個CPU之間沒有區(qū)別,平等地訪問內(nèi)存、外設(shè)、一個操作系統(tǒng)。操作系統(tǒng)管理著一個隊列,每個處理器依次處理隊列中的進程。如果兩個處理器同時請求訪問一個資源(例如同一段內(nèi)存地址),由硬件、軟件的鎖機制去解決資源爭用問題。SMP 服務(wù)器的主要特征是共享,系統(tǒng)中所有資源(CPU 、內(nèi)存、I/O 等)都是共享的。也正是由于這種特征,導(dǎo)致了 SMP 服務(wù)器的主要問題,那就是它的擴展能力非常有限。對于 SMP 服務(wù)器而言,每一個共享的環(huán)節(jié)都可能造成 SMP 服務(wù)器擴展時的瓶頸,而最受限制的則是內(nèi)存。由于每個 CPU 必須通過相同的內(nèi)存總線訪問相同的內(nèi)存資源,因此隨著 CPU 數(shù)量的增加,內(nèi)存訪問沖突將迅速增加,最終會造成 CPU 資源的浪費,使 CPU 性能的有效性大大降低。實驗證明,SMP 服務(wù)器 CPU 利用率最好的情況是2至4個 CPU 。
NUMA 服務(wù)器的基本特征是具有多個 CPU 模塊,每個 CPU 模塊由多個 CPU (如4個)組成,并且具有獨立的本地內(nèi)存、 I/O 槽口等。由于其節(jié)點之間可以通過互聯(lián)模塊(如稱為Crossbar Switch)進行連接和信息交互,因此每個 CPU 可以訪問整個系統(tǒng)的內(nèi)存 (這是 NUMA 系統(tǒng)與 MPP 系統(tǒng)的重要差別)。顯然,訪問本地內(nèi)存的速度將遠遠高于訪問遠地內(nèi)存(系統(tǒng)內(nèi)其它節(jié)點的內(nèi)存)的速度,這也是非一致存儲訪問 NUMA 的由來。由于這個特點,為了更好地發(fā)揮系統(tǒng)性能,開發(fā)應(yīng)用程序時需要盡量減少不同 CPU 模塊之間的信息交互。利用 NUMA 技術(shù),可以較好地解決原來 SMP 系統(tǒng)的擴展問題,在一個物理服務(wù)器內(nèi)可以支持上百個 CPU 。NUMA 技術(shù)同樣有一定缺陷,由于訪問遠地內(nèi)存的延時遠遠超過本地內(nèi)存,因此當(dāng) CPU 數(shù)量增加時,系統(tǒng)性能無法線性增加。
和 NUMA 不同,MPP提供了另外一種進行系統(tǒng)擴展的方式,它由多個 SMP 服務(wù)器通過一定的節(jié)點互聯(lián)網(wǎng)絡(luò)進行連接,協(xié)同工作,完成相同的任務(wù),從用戶的角度來看是一個服務(wù)器系統(tǒng)。其基本特征是由多個SMP服務(wù)器(每個 SMP 服務(wù)器稱節(jié)點)通過節(jié)點互聯(lián)網(wǎng)絡(luò)連接而成,每個節(jié)點只訪問自己的本地資源(內(nèi)存、存儲等),是一種完全無共享(Share Nothing)結(jié)構(gòu),因而擴展能力最好,理論上其擴展無限制,目前的技術(shù)可實現(xiàn) 512 個節(jié)點互聯(lián),數(shù)千個 CPU 。MPP不是處理器內(nèi)部節(jié)點互聯(lián),而是多個服務(wù)器通過外部互聯(lián)。在 MPP 系統(tǒng)中,每個 SMP 節(jié)點也可以運行自己的操作系統(tǒng)、數(shù)據(jù)庫等。但和 NUMA 不同的是,它不存在異地內(nèi)存訪問的問題。換言之,每個節(jié)點內(nèi)的CPU不能訪問另一個節(jié)點的內(nèi)存。節(jié)點之間的信息交互是通過節(jié)點互聯(lián)網(wǎng)絡(luò)實現(xiàn)的,這個過程一般稱為數(shù)據(jù)重分配(Data Redistribution)。MPP服務(wù)器需要一種復(fù)雜的機制來調(diào)度和平衡各個節(jié)點的負載和并行處理過程。
NUMA系統(tǒng)是一種多處理器環(huán)境下設(shè)計的內(nèi)存結(jié)構(gòu)。在NUMA架構(gòu)出現(xiàn)前,CPU歡快的朝著頻率越來越高的方向發(fā)展。受到物理極限的挑戰(zhàn),又轉(zhuǎn)為核數(shù)越來越多的方向發(fā)展。如果每個core的工作性質(zhì)都是share-nothing(類似于map-reduce的node節(jié)點的作業(yè)屬性),那么也許就不會有NUMA。由于所有CPU Core都是通過共享一個北橋來讀取內(nèi)存,無論核數(shù)如何的發(fā)展,北橋在響應(yīng)時間上的性能瓶頸越來越明顯。于是,聰明的硬件設(shè)計師們,想到了把內(nèi)存控制器(原本北橋中讀取內(nèi)存的部分)也做個拆分,平分到了每個die上。于是NUMA就出現(xiàn)了!
NUMA中,雖然內(nèi)存直接attach在CPU上,但是由于內(nèi)存被平均分配在了各個die上。只有當(dāng)CPU訪問自身直接attach內(nèi)存對應(yīng)的物理地址時,才會有較短的響應(yīng)時間(后稱Local Access)。而如果需要訪問其他CPU attach的內(nèi)存的數(shù)據(jù)時,就需要通過inter-connect通道訪問,響應(yīng)時間就相比之前變慢了(后稱Remote Access)。所以NUMA(Non-Uniform Memory Access)就此得名。
NUMA的幾個概念(Node,socket,core,thread)
socket:就是主板上的CPU插槽;
Core:就是socket里獨立的一組程序執(zhí)行的硬件單元,比如寄存器,計算單元等;
Thread:就是超線程hyperthread的概念,邏輯的執(zhí)行單元,獨立的執(zhí)行上下文,但是共享core內(nèi)的寄存器和計算單元。
Node:這個概念其實是用來解決core的分組的問題,具體參見下圖來理解(圖中的OS CPU可以理解thread,那么core就沒有在圖中畫出),從圖中可以看出共有4個socket,每個socket 2個node,每個node中有8個thread,總共4(Socket)× 2(Node)× 8(4core × 2 Thread) = 64個thread。另外每個node有自己的內(nèi)部CPU,總線和內(nèi)存,同時還可以訪問其他node內(nèi)的內(nèi)存,NUMA的最大的優(yōu)勢就是可以方便的增加CPU的數(shù)量,因為Node內(nèi)有自己內(nèi)部總線,所以增加CPU數(shù)量可以通過增加Node的數(shù)目來實現(xiàn),如果單純的增加CPU的數(shù)量,會對總線造成很大的壓力,所以UMA結(jié)構(gòu)不可能支持很多的核。下圖出自:《NUMA Best Practices for Dell PowerEdge 12th Generation Servers》。
由于每個node內(nèi)部有自己的CPU總線和內(nèi)存,所以如果一個虛擬機的vCPU跨不同的Node的話,就會導(dǎo)致一個node中的CPU去訪問另外一個node中的內(nèi)存的情況,這就導(dǎo)致內(nèi)存訪問延遲的增加。在NFV環(huán)境中,對性能有比較高的要求,就非常需要同一個虛擬機的vCPU盡量被分配到同一個Node中的pCPU上,所以在OpenStack的Kilo版本及后續(xù)版本均增加了基于NUMA感知的虛擬機調(diào)度的特性。
查看服務(wù)器中NUMA拓撲架構(gòu)常用以下命令:
1)比較常用的是lscpu
[root@C7-Server01 ~]# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 1
Core(s) per socket: 2
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 158
Model name: Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz
Stepping: 10
CPU MHz: 2903.998
BogoMIPS: 5807.99
Virtualization: VT-x
Hypervisor vendor: VMware
Virtualization type: full
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 12288K
NUMA node0 CPU(s): 0-3
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon nopl xtopology tsc_reliable nonstop_tsc eagerfpu pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch ssbd ibrs ibpb stibp tpr_shadow vnmi ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 invpcid rtm mpx rdseed adx smap clflushopt xsaveopt xsavec arat spec_ctrl intel_stibp flush_l1d arch_capabilities
從上面報文輸出可以看出,當(dāng)前機器有2個sockets,每個sockets包含1個numa node,每個numa node中有2個cores,每個cores包含1個thread,所以總的threads數(shù)量=2(sockets)×1(node)×2(cores)×1(threads)=4.
2)通過shell腳本打印出當(dāng)前機器的socket,core和thread的數(shù)量
#!/bin/bash
# 簡單打印系統(tǒng)CPU拓撲
# Author: kkutysllb
function get_nr_processor()
{
grep '^processor' /proc/cpuinfo | wc -l
}
function get_nr_socket()
{
grep 'physical id' /proc/cpuinfo | awk -F: '{
print $2 | "sort -un"}' | wc -l
}
function get_nr_siblings()
{
grep 'siblings' /proc/cpuinfo | awk -F: '{
print $2 | "sort -un"}'
}
function get_nr_cores_of_socket()
{
grep 'cpu cores' /proc/cpuinfo | awk -F: '{
print $2 | "sort -un"}'
}
echo '===== CPU Topology Table ====='
echo
echo '+--------------+---------+-----------+'
echo '| Processor ID | Core ID | Socket ID |'
echo '+--------------+---------+-----------+'
while read line; do
if [ -z "$line" ]; then
printf '| %-12s | %-7s | %-9s |n' $p_id $c_id $s_id
echo '+--------------+---------+-----------+'
continue
fi
if echo "$line" | grep -q "^processor"; then
p_id=`echo "$line" | awk -F: '{print $2}' | tr -d ' '`
fi
if echo "$line" | grep -q "^core id"; then
c_id=`echo "$line" | awk -F: '{print $2}' | tr -d ' '`
fi
if echo "$line" | grep -q "^physical id"; then
s_id=`echo "$line" | awk -F: '{print $2}' | tr -d ' '`
fi
done < /proc/cpuinfo
echo
awk -F: '{
if ($1 ~ /processor/) {
gsub(/ /,"",$2);
p_id=$2;
} else if ($1 ~ /physical id/){
gsub(/ /,"",$2);
s_id=$2;
arr[s_id]=arr[s_id] " " p_id
}
}
END{
for (i in arr)
printf "Socket %s:%sn", i, arr[i];
}' /proc/cpuinfo
echo
echo '===== CPU Info Summary ====='
echo
nr_processor=`get_nr_processor`
echo "Logical processors: $nr_processor"
nr_socket=`get_nr_socket`
echo "Physical socket: $nr_socket"
nr_siblings=`get_nr_siblings`
echo "Siblings in one socket: $nr_siblings"
nr_cores=`get_nr_cores_of_socket`
echo "Cores in one socket: $nr_cores"
let nr_cores*=nr_socket
echo "Cores in total: $nr_cores"
if [ "$nr_cores" = "$nr_processor" ]; then
echo "Hyper-Threading: off"
else
echo "Hyper-Threading: on"
fi
echo
echo '===== END ====='
輸出結(jié)果如下:
DPDK中有以下策略來適應(yīng)NUMA系統(tǒng):
1)Per-core memory:一個CPU上有多個核(core),per-core memory是指每個核都有屬于自己的內(nèi)存,即對于經(jīng)常訪問的數(shù)據(jù)結(jié)構(gòu),每個核都有自己的備份。這樣做一方面是為了本地內(nèi)存的需要,另外一方面也是前面提到的Cache一致性的需要,避免多個核訪問同一個Cache Line。
2)本地設(shè)備本地處理:即用本地的處理器、本地的內(nèi)存來處理本地的設(shè)備上產(chǎn)生的數(shù)據(jù)。如果有一個PCI設(shè)備在node0上,就用node0上的核來處理該設(shè)備,處理該設(shè)備用到的數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)緩沖區(qū)都從node0上分配。以下是一個分配本地內(nèi)存的例子:
/* allocate memory for the queue structure */ // 該例分配一個結(jié)構(gòu)體,通過傳遞socket_id,即node id獲得本地內(nèi)存,并且以Cache Line對齊。q = rte_zmalloc_socket("fm10k", sizeof(*q), RTE_CACHE_LINE_SIZE, socket_id);
CPU的親和性調(diào)度
當(dāng)前,屬于多核處理器時代,這類多核處理器自然會面對一個問題,按照什么策略將任務(wù)線程分配到各個處理器上執(zhí)行。眾所周知,這個分配工作一般由操作系統(tǒng)完成。負載均衡當(dāng)然是比較理想的策略,按需指定的方式也是很自然的訴求,因為其具有確定性。簡單地說,CPU親和性(Core affinity)就是一個特定的任務(wù)要在某個給定的CPU上盡量長時間地運行而不被遷移到其他處理器上的傾向性。這意味著線程可以不在處理器之間頻繁遷移,從而減少不必要的開銷。
Linux內(nèi)核包含了一種機制,它讓開發(fā)人員可以編程實現(xiàn)CPU親和性。也就是說可以將應(yīng)用程序顯式地指定線程在哪個(或哪些)CPU上運行。
在Linux內(nèi)核中,所有的線程都有一個相關(guān)的數(shù)據(jù)結(jié)構(gòu),稱為task_struct。這個結(jié)構(gòu)非常重要,這里不展開討論,只討論其中與親和性相關(guān)度最高的是cpus_allowed位掩碼。這個位掩碼由n位組成,與系統(tǒng)中的n個邏輯處理器一一對應(yīng)。具有4個物理CPU的系統(tǒng)可以有4位。如果這些CPU都啟用了超線程,那么這個系統(tǒng)就有一個8位的位掩碼。
如果針對某個線程設(shè)置了指定的位,那么這個線程就可以在相關(guān)的CPU上運行。因此,如果一個線程可以在任何CPU上運行,并且能夠根據(jù)需要在處理器之間進行遷移,那么位掩碼就全是1。實際上,在Linux中,這就是線程的默認狀態(tài)。
Linux內(nèi)核API提供了一些方法,讓用戶可以修改位掩碼或查看當(dāng)前的位掩碼:
sched_set_affinity()(用來修改位掩碼)
sched_get_affinity()(用來查看當(dāng)前的位掩碼)
注意,cpu_affinity會被傳遞給子線程,因此應(yīng)該適當(dāng)?shù)卣{(diào)用sched_set_affinity。
將線程與CPU綁定,最直觀的好處就是提高了CPU Cache的命中率,從而減少內(nèi)存訪問損耗,提高程序的速度。在多核體系CPU上,提高外設(shè)以及程序工作效率最直觀的辦法就是讓各個物理核各自負責(zé)專門的事情。尤其在在NUMA架構(gòu)下,這個操作對系統(tǒng)運行速度的提升有更大的意義,跨NUMA節(jié)點的任務(wù)切換,將導(dǎo)致大量三級Cache的丟失。從這個角度來看,NUMA使用CPU綁定時,每個核心可以更專注地處理一件事情,資源體系被充分使用,減少了同步的損耗。
通常Linux內(nèi)核都可以很好地對線程進行調(diào)度,在應(yīng)該運行的地方運行線程,也就是說在可用的處理器上運行并獲得很好的整體性能。內(nèi)核包含了一些用來檢測CPU之間任務(wù)負載遷移的算法,可以啟用線程遷移來降低繁忙的處理器的壓力。只有在以下三個特殊場景會用到CPU親和性綁定機制:
大量計算:在科學(xué)計算和理論計算中,如果不進行CPU親和性綁定,會發(fā)現(xiàn)自己的應(yīng)用程序要在多處理器的機器上花費大量時間進行遷移從而完成計算。
復(fù)雜程序測試:比如在線性可伸縮測試中,我們期望的理論模型是如果應(yīng)用程序隨著CPU的增加可以線性地伸縮,那么每秒事務(wù)數(shù)和CPU個數(shù)之間應(yīng)該會是線性的關(guān)系。這樣建模可以測試應(yīng)用程序是否可以有效地使用底層硬件。如果一個給定的線程遷移到其他地方去了,那么它就失去了利用CPU緩存的優(yōu)勢。實際上,如果正在使用的CPU需要為自己緩存一些特殊的數(shù)據(jù),那么其他所有CPU都會使這些數(shù)據(jù)在自己的緩存中失效。因此,如果有多個線程都需要相同的數(shù)據(jù),那么將這些線程綁定到一個特定的CPU上,就可以確保它們訪問相同的緩存數(shù)據(jù)或者至少可以提高緩存的命中率。
實時性線程:對于實時性線程經(jīng)常會希望使用親和性來指定一個8路主機上的某個CPU來處理,而同時允許其他7個CPU處理所有普通的系統(tǒng)調(diào)度。這種做法對長時間運行、對時間敏感的應(yīng)用程序可以確保正常運行,同時可以允許其他應(yīng)用程序獨占其余的計算資源。
Linux內(nèi)核提供了啟動參數(shù)isolcpus。對于有4個CPU的服務(wù)器,在啟動的時候加入啟動參數(shù)isolcpus=2,3。那么系統(tǒng)啟動后將不使用CPU3和CPU4。注意,這里說的不使用不是絕對地不使用,系統(tǒng)啟動后仍然可以通過taskset命令指定哪些程序在這些核心中運行。
1)修改/etc/default/grub文件中內(nèi)容,在CMDLINE中添加如下圖所示設(shè)置
2)編譯內(nèi)核啟動文件
[root@C7-Server01 myshell]# grub2-mkconfig -o /boot/grub2/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-3.10.0-957.10.1.el7.x86_64
Found initrd image: /boot/initramfs-3.10.0-957.10.1.el7.x86_64.img
Found linux image: /boot/vmlinuz-3.10.0-862.el7.x86_64
Found initrd image: /boot/initramfs-3.10.0-862.el7.x86_64.img
Found linux image: /boot/vmlinuz-0-rescue-e344b139f44946638783478bcb51f820
Found initrd image: /boot/initramfs-0-rescue-e344b139f44946638783478bcb51f820.img
done
3)重啟系統(tǒng)后查看/proc/cmdline配置文件是否設(shè)置生效
[root@C7-Server01 ~]# cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-3.10.0-957.10.1.el7.x86_64 root=UUID=0887567f-1df6-425f-ba3d-ce58584279e0 ro crashkernel=auto biosdevname=0 net.ifnames=0 rhgb quiet isolcpu=2,3
DPDK的線程基于pthread接口創(chuàng)建,屬于搶占式線程模型,受內(nèi)核調(diào)度支配。DPDK通過在多核設(shè)備上創(chuàng)建多個線程,每個線程綁定到單獨的核上,減少線程調(diào)度的開銷,以提高性能。DPDK的線程可以作為控制線程,也可以作為數(shù)據(jù)線程??刂凭€程一般綁定到MASTER核上,接受用戶配置,并傳遞配置參數(shù)給數(shù)據(jù)線程等;數(shù)據(jù)線程分布在不同核上處理數(shù)據(jù)包。
DPDK的lcore指的是EAL線程,本質(zhì)是基于pthread(Linux/FreeBSD)封裝實現(xiàn)。Lcore(EAL pthread)由remote_launch函數(shù)指定的任務(wù)創(chuàng)建并管理。在每個EAL pthread中,有一個TLS(Thread Local Storage)稱為_lcore_id。當(dāng)使用DPDK的EAL‘-c’參數(shù)指定coremask時,EAL pthread生成相應(yīng)個數(shù)lcore,并默認是1:1親和到coremask對應(yīng)的CPU邏輯核,_lcore_id和CPU ID是一致的。
// rte_eal_cpu_init()函數(shù)中,通過讀取/sys/devices/system/cpu/cpuX/下的相關(guān)信息,確定當(dāng)前系統(tǒng)有哪些CPU核,以及每個核屬于哪個CPU Socket。// eal_parse_args()函數(shù),解析-c參數(shù),確認哪些CPU核是可以使用的,以及設(shè)置第一個核為MASTER。// 為每一個SLAVE核創(chuàng)建線程,并調(diào)用eal_thread_set_affinity()綁定CPU。// 線程的執(zhí)行體是eal_thread_loop(),函數(shù)內(nèi)部的主體是一個while死循環(huán),調(diào)用不同模塊注冊到lcore_config[lcore_id].f的回調(diào)函數(shù)。
RTE_LCORE_FOREACH_SLAVE(i)
{
/* * create communication pipes between master thread * and children */
if (pipe(lcore_config[i].pipe_master2slave) < 0)
rte_panic("Cannot create pipen");
if (pipe(lcore_config[i].pipe_slave2master) < 0)
rte_panic("Cannot create pipen");
lcore_config[i].state = WAIT;
/* create a thread for each lcore */
ret = pthread_create(&lcore_config[i].thread_id, NULL, eal_thread_loop, NULL);
if (ret!= 0)
rte_panic("Cannot create threadn");
}
// 不同的模塊需要調(diào)用rte_eal_mp_remote_launch(),將自己的回調(diào)處理函數(shù)注冊到lcore_config[].f中。// 以l2fwd為例,注冊的回調(diào)處理函數(shù)是l2fwd_launch_on_lcore()。rte_eal_mp_remote_launch(l2fwd_launch_one_lcore, NULL, CALL_MASTER);
DPDK每個核上的線程最終會調(diào)用eal_thread_loop()>>> l2fwd_launch_on_lcore(),調(diào)用到自己實現(xiàn)的處理函數(shù)。默認情況下,lcore是與邏輯核一一親和綁定的。帶來性能提升的同時,也犧牲了一定的靈活性和能效。在現(xiàn)網(wǎng)中,往往有流量潮汐現(xiàn)象的發(fā)生,在網(wǎng)絡(luò)流量空閑時,沒有必要使用與流量繁忙時相同的核數(shù)。于是,EAL pthread和邏輯核之間進而允許打破1:1的綁定關(guān)系,使得_lcore_id本身和CPU ID可以不嚴格一致。EAL定義了長選項“——lcores”來指定lcore的CPU親和性。對一個特定的lcore ID或者lcore ID組,這個長選項允許為EAL pthread設(shè)置CPU集。這個選項以及對應(yīng)的一組API(rte_thread_set/get_affinity())為lcore提供了親和的靈活性。lcore可以親和到一個CPU或者一個CPU集合,使得在運行時調(diào)整具體某個CPU承載lcore成為可能。同時,多個lcore也可能親和到同一個核,但是這種情況下如果調(diào)度占用的內(nèi)核庫是非搶占式,就存在鎖機制,DPDK技術(shù)棧在電信云中的最佳實踐(3)中會專門針對不同鎖進制進行討論。
除了使用DPDK提供的邏輯核之外,用戶也可以將DPDK的執(zhí)行上下文運行在任何用戶自己創(chuàng)建的pthread中。在普通用戶自定義的pthread中,lcore id的值總是LCORE_ID_ANY,以此確定這個thread是一個有效的普通用戶所創(chuàng)建的pthread。用戶創(chuàng)建的pthread可以支持絕大多數(shù)DPDK庫,沒有任何影響。但少數(shù)DPDK庫可能無法完全支持用戶自創(chuàng)建的pthread,如timer和Mempool。詳細請參見《DPDK開發(fā)者手冊多線程章節(jié)》。
DPDK不僅可以通過綁核完成大量計算任務(wù)資源親和性調(diào)度,同時在計算任務(wù)較小,一個核的資源綽綽有余的情況下,還可以通過Linux的cgroup對資源進行釋放。因為,DPDK的線程其實就是普通的pthread,其本質(zhì)就是使用cgroup能把CPU的配額靈活地配置在不同的線程上。因此,DPDK可以借助cgroup實現(xiàn)計算資源配額對于線程的靈活配置,可以有效改善I/O核的閑置利用率。
最后,用一張圖來總結(jié)lcore的啟動過程和執(zhí)行任務(wù)分發(fā)的流程。