接上篇《NFV關(guān)鍵技術(shù):DPDK技術(shù)棧在網(wǎng)絡(luò)云中的最佳實(shí)踐01》
4、DDIO(Data Direct I/O)數(shù)據(jù)直連技術(shù)
如今,隨著大數(shù)據(jù)和云計(jì)算的爆炸式增長(zhǎng),寬帶的普及以及個(gè)人終端網(wǎng)絡(luò)數(shù)據(jù)的日益提高,對(duì)運(yùn)營(yíng)商服務(wù)節(jié)點(diǎn)和數(shù)據(jù)中心的數(shù)據(jù)交換能力和網(wǎng)絡(luò)帶寬提出了更高的要求。并且,數(shù)據(jù)中心本身對(duì)虛擬化功能的需求也增加了更多的網(wǎng)絡(luò)帶寬需求。為此,英特爾公司提出了Intel® DDIO(Data Direct I/O)的技術(shù)。該技術(shù)的主要目的就是讓服務(wù)器能更快處理網(wǎng)絡(luò)接口的數(shù)據(jù),提高系統(tǒng)整體的吞吐率,降低延遲,同時(shí)減少能源的消耗。
當(dāng)一個(gè)網(wǎng)絡(luò)報(bào)文送到服務(wù)器的網(wǎng)卡時(shí),網(wǎng)卡通過(guò)外部總線(比如PCI總線)把數(shù)據(jù)和報(bào)文描述符送到內(nèi)存。接著,CPU從內(nèi)存讀取數(shù)據(jù)到Cache進(jìn)而到寄存器。進(jìn)行處理之后,再寫回到Cache,并最終送到內(nèi)存中。最后,網(wǎng)卡讀取內(nèi)存數(shù)據(jù),經(jīng)過(guò)外部總線送到網(wǎng)卡內(nèi)部,最終通過(guò)網(wǎng)絡(luò)接口發(fā)送出去。可以看出,對(duì)于一個(gè)數(shù)據(jù)報(bào)文,CPU和網(wǎng)卡需要多次訪問(wèn)內(nèi)存。而內(nèi)存相對(duì)CPU來(lái)講是一個(gè)非常慢速的部件。CPU需要等待數(shù)百個(gè)周期才能拿到數(shù)據(jù),在這過(guò)程中,CPU什么也做不了。
DDIO技術(shù)思想就是使外部網(wǎng)卡和CPU通過(guò)LLC Cache直接交換數(shù)據(jù),繞過(guò)了內(nèi)存這個(gè)相對(duì)慢速的部件。這樣,就增加了CPU處理網(wǎng)絡(luò)報(bào)文的速度(減少了CPU和網(wǎng)卡等待內(nèi)存的時(shí)間),減小了網(wǎng)絡(luò)報(bào)文在服務(wù)器端的處理延遲。這樣做也帶來(lái)了一個(gè)問(wèn)題,就是網(wǎng)絡(luò)報(bào)文直接存儲(chǔ)在LLC Cache中,對(duì)這一級(jí)cache的容量有很大需求。因此,在英特爾的E5處理器系列產(chǎn)品中,把LLC Cache的容量提高到了20MB。DDIO處理網(wǎng)絡(luò)報(bào)文流程示意圖如下:
為了發(fā)送一個(gè)數(shù)據(jù)報(bào)文到網(wǎng)絡(luò)上去,首先是運(yùn)行在CPU上的軟件分配了一段內(nèi)存,然后把這段內(nèi)存讀取到CPU內(nèi)部,更新數(shù)據(jù),并且填充相應(yīng)的報(bào)文描述符(網(wǎng)卡會(huì)通過(guò)讀取描述符了解報(bào)文的相應(yīng)信息),然后寫回到內(nèi)存中,通知網(wǎng)卡,最終網(wǎng)卡把數(shù)據(jù)讀回到內(nèi)部,并且發(fā)送到網(wǎng)絡(luò)上去。但是,沒(méi)有DDIO技術(shù)和有DDIO技術(shù)條件的處理方式是不同的。
a) 沒(méi)有DDIO時(shí),如上圖所示:
1)CPU更新報(bào)文和控制結(jié)構(gòu)體。由于分配的緩沖區(qū)在內(nèi)存中,因此會(huì)觸發(fā)一次Cache不命中,CPU把內(nèi)存讀取到Cache中,然后更新控制結(jié)構(gòu)體和報(bào)文信息。之后通知NIC來(lái)讀取報(bào)文。
2)NIC收到有報(bào)文需要傳遞到網(wǎng)絡(luò)上的通知后,讀取控制結(jié)構(gòu)體進(jìn)而知道去內(nèi)存中讀取報(bào)文信息。
3)由于之前CPU剛把該緩沖區(qū)從內(nèi)存讀到Cache中并且做了更新,很有可能Cache還沒(méi)有來(lái)得及把更新的內(nèi)容寫回到內(nèi)存中(回寫機(jī)制)。因此,當(dāng)NIC發(fā)起一個(gè)對(duì)內(nèi)存的讀請(qǐng)求時(shí),很有可能這個(gè)請(qǐng)求會(huì)發(fā)送到Cache系統(tǒng)中,Cache系統(tǒng)會(huì)把數(shù)據(jù)寫回到內(nèi)存中。
4)嘴周,內(nèi)存控制器再把數(shù)據(jù)寫到PCI總線上去,NIC從PCI總線上讀取數(shù)據(jù)。
b) 有DDIO時(shí),如上圖所示:
1)CPU更新報(bào)文和控制結(jié)構(gòu)體。這個(gè)步驟和沒(méi)有DDIO的技術(shù)類似,但是由于DDIO的引入,處理器會(huì)開(kāi)始就把內(nèi)存中的緩沖區(qū)和控制結(jié)構(gòu)體預(yù)取到Cache,因此減少了內(nèi)存讀的時(shí)間。
2)NIC收到有報(bào)文需要傳遞到網(wǎng)絡(luò)上的通知后,通過(guò)PCI總線去讀取控制結(jié)構(gòu)體和報(bào)文。利用DDIO技術(shù),I/O訪問(wèn)可以直接將Cache的內(nèi)容送到PCI總線上。這樣,就減少了Cache寫回時(shí)等待的時(shí)間。
由此可以看出,由于DDIO技術(shù)的引入,網(wǎng)卡的讀操作減少了訪問(wèn)內(nèi)存的次數(shù),因而提高了訪問(wèn)效率,減少了報(bào)文轉(zhuǎn)發(fā)的延遲。在理想狀況下,NIC和CPU無(wú)需訪問(wèn)內(nèi)存,直接通過(guò)訪問(wèn)Cache就可以完成更新數(shù)據(jù),把數(shù)據(jù)送到NIC內(nèi)部,進(jìn)而送到網(wǎng)絡(luò)上的所有操作。
有網(wǎng)絡(luò)報(bào)文需要送到系統(tǒng)內(nèi)部進(jìn)行處理,其過(guò)程一般是NIC從網(wǎng)絡(luò)上收到報(bào)文后,通過(guò)PCI總線把報(bào)文和相應(yīng)的控制結(jié)構(gòu)體送到預(yù)先分配的內(nèi)存,然后通知相應(yīng)的驅(qū)動(dòng)程序或者軟件來(lái)處理。和之前網(wǎng)卡的讀數(shù)據(jù)操作類似,有DDIO技術(shù)和沒(méi)有DDIO技術(shù)的處理也是不一樣的。
a) 沒(méi)有DDIO時(shí),如上圖所示:
1)報(bào)文和控制結(jié)構(gòu)體通過(guò)PCI總線送到指定的內(nèi)存中。如果該內(nèi)存恰好緩存在Cache中(有可能之前CPU有對(duì)該內(nèi)存進(jìn)行過(guò)讀寫操作),則需要等待Cache把內(nèi)容先寫回到內(nèi)存中,然后才能把報(bào)文和控制結(jié)構(gòu)體寫到內(nèi)存中。
2)運(yùn)行在CPU上的驅(qū)動(dòng)程序或者軟件得到通知收到新報(bào)文,去內(nèi)存中讀取控制結(jié)構(gòu)體和相應(yīng)的報(bào)文,Cache不命中。之所以Cache一定不會(huì)命中,是因?yàn)榧词乖搩?nèi)存地址在Cache中,在步驟1中也被強(qiáng)制寫回到內(nèi)存中。因此,只能從內(nèi)存中讀取控制結(jié)構(gòu)體和報(bào)文。
b) 有DDIO時(shí),如上圖所示:
1)這時(shí),報(bào)文和控制結(jié)構(gòu)體通過(guò)PCI總線直接送到Cache中。這時(shí)有兩種情形:場(chǎng)景一就是如果該內(nèi)存恰好緩存在Cache中(有可能之前處理器有對(duì)該內(nèi)存進(jìn)行過(guò)讀寫操作),則直接在Cache中更新內(nèi)容,覆蓋原有內(nèi)容。場(chǎng)景二就是如果該內(nèi)存沒(méi)有緩存在Cache中,則在最后一級(jí)Cache中分配一塊區(qū)域,并相應(yīng)更新Cache表,表明該內(nèi)容是對(duì)應(yīng)于內(nèi)存中的某個(gè)地址的。
2)運(yùn)行在CPU上的驅(qū)動(dòng)或者軟件被通知到有報(bào)文到達(dá),其產(chǎn)生一個(gè)內(nèi)存讀操作,由于該內(nèi)容已經(jīng)在Cache中,因此直接從Cache中讀。
由此可以看出,DDIO技術(shù)在CPU和外設(shè)之間交換數(shù)據(jù)時(shí),減少了CPU和外設(shè)訪問(wèn)內(nèi)存的次數(shù),也減少了Cache寫回的等待,提高了系統(tǒng)的吞吐率和數(shù)據(jù)的交換延遲。
NUMA系統(tǒng)
從系統(tǒng)架構(gòu)來(lái)看,目前的商用服務(wù)器大體可以分為三類,即對(duì)稱多處理器結(jié)構(gòu)(SMP :Symmetric Multi-Processor),非一致存儲(chǔ)訪問(wèn)結(jié)構(gòu)(NUMA :Non-Uniform Memory Access),以及海量并行處理結(jié)構(gòu)(MPP :Massive Parallel Processing)。它們的特征如下:
SMP (Symmetric Multi Processing),對(duì)稱多處理系統(tǒng)內(nèi)有許多緊耦合多處理器,在這樣的系統(tǒng)中,所有的CPU共享全部資源,如總線,內(nèi)存和I/O系統(tǒng)等,操作系統(tǒng)或管理數(shù)據(jù)庫(kù)的復(fù)本只有一個(gè),這種系統(tǒng)有一個(gè)最大的特點(diǎn)就是共享所有資源。多個(gè)CPU之間沒(méi)有區(qū)別,平等地訪問(wèn)內(nèi)存、外設(shè)、一個(gè)操作系統(tǒng)。操作系統(tǒng)管理著一個(gè)隊(duì)列,每個(gè)處理器依次處理隊(duì)列中的進(jìn)程。如果兩個(gè)處理器同時(shí)請(qǐng)求訪問(wèn)一個(gè)資源(例如同一段內(nèi)存地址),由硬件、軟件的鎖機(jī)制去解決資源爭(zhēng)用問(wèn)題。SMP 服務(wù)器的主要特征是共享,系統(tǒng)中所有資源(CPU 、內(nèi)存、I/O 等)都是共享的。也正是由于這種特征,導(dǎo)致了 SMP 服務(wù)器的主要問(wèn)題,那就是它的擴(kuò)展能力非常有限。對(duì)于 SMP 服務(wù)器而言,每一個(gè)共享的環(huán)節(jié)都可能造成 SMP 服務(wù)器擴(kuò)展時(shí)的瓶頸,而最受限制的則是內(nèi)存。由于每個(gè) CPU 必須通過(guò)相同的內(nèi)存總線訪問(wèn)相同的內(nèi)存資源,因此隨著 CPU 數(shù)量的增加,內(nèi)存訪問(wèn)沖突將迅速增加,最終會(huì)造成 CPU 資源的浪費(fèi),使 CPU 性能的有效性大大降低。實(shí)驗(yàn)證明,SMP 服務(wù)器 CPU 利用率最好的情況是2至4個(gè) CPU 。
NUMA 服務(wù)器的基本特征是具有多個(gè) CPU 模塊,每個(gè) CPU 模塊由多個(gè) CPU (如4個(gè))組成,并且具有獨(dú)立的本地內(nèi)存、 I/O 槽口等。由于其節(jié)點(diǎn)之間可以通過(guò)互聯(lián)模塊(如稱為Crossbar Switch)進(jìn)行連接和信息交互,因此每個(gè) CPU 可以訪問(wèn)整個(gè)系統(tǒng)的內(nèi)存 (這是 NUMA 系統(tǒng)與 MPP 系統(tǒng)的重要差別)。顯然,訪問(wèn)本地內(nèi)存的速度將遠(yuǎn)遠(yuǎn)高于訪問(wèn)遠(yuǎn)地內(nèi)存(系統(tǒng)內(nèi)其它節(jié)點(diǎn)的內(nèi)存)的速度,這也是非一致存儲(chǔ)訪問(wèn) NUMA 的由來(lái)。由于這個(gè)特點(diǎn),為了更好地發(fā)揮系統(tǒng)性能,開(kāi)發(fā)應(yīng)用程序時(shí)需要盡量減少不同 CPU 模塊之間的信息交互。利用 NUMA 技術(shù),可以較好地解決原來(lái) SMP 系統(tǒng)的擴(kuò)展問(wèn)題,在一個(gè)物理服務(wù)器內(nèi)可以支持上百個(gè) CPU 。NUMA 技術(shù)同樣有一定缺陷,由于訪問(wèn)遠(yuǎn)地內(nèi)存的延時(shí)遠(yuǎn)遠(yuǎn)超過(guò)本地內(nèi)存,因此當(dāng) CPU 數(shù)量增加時(shí),系統(tǒng)性能無(wú)法線性增加。
和 NUMA 不同,MPP提供了另外一種進(jìn)行系統(tǒng)擴(kuò)展的方式,它由多個(gè) SMP 服務(wù)器通過(guò)一定的節(jié)點(diǎn)互聯(lián)網(wǎng)絡(luò)進(jìn)行連接,協(xié)同工作,完成相同的任務(wù),從用戶的角度來(lái)看是一個(gè)服務(wù)器系統(tǒng)。其基本特征是由多個(gè)SMP服務(wù)器(每個(gè) SMP 服務(wù)器稱節(jié)點(diǎn))通過(guò)節(jié)點(diǎn)互聯(lián)網(wǎng)絡(luò)連接而成,每個(gè)節(jié)點(diǎn)只訪問(wèn)自己的本地資源(內(nèi)存、存儲(chǔ)等),是一種完全無(wú)共享(Share Nothing)結(jié)構(gòu),因而擴(kuò)展能力最好,理論上其擴(kuò)展無(wú)限制,目前的技術(shù)可實(shí)現(xiàn) 512 個(gè)節(jié)點(diǎn)互聯(lián),數(shù)千個(gè) CPU 。MPP不是處理器內(nèi)部節(jié)點(diǎn)互聯(lián),而是多個(gè)服務(wù)器通過(guò)外部互聯(lián)。在 MPP 系統(tǒng)中,每個(gè) SMP 節(jié)點(diǎn)也可以運(yùn)行自己的操作系統(tǒng)、數(shù)據(jù)庫(kù)等。但和 NUMA 不同的是,它不存在異地內(nèi)存訪問(wèn)的問(wèn)題。換言之,每個(gè)節(jié)點(diǎn)內(nèi)的CPU不能訪問(wèn)另一個(gè)節(jié)點(diǎn)的內(nèi)存。節(jié)點(diǎn)之間的信息交互是通過(guò)節(jié)點(diǎn)互聯(lián)網(wǎng)絡(luò)實(shí)現(xiàn)的,這個(gè)過(guò)程一般稱為數(shù)據(jù)重分配(Data Redistribution)。MPP服務(wù)器需要一種復(fù)雜的機(jī)制來(lái)調(diào)度和平衡各個(gè)節(jié)點(diǎn)的負(fù)載和并行處理過(guò)程。
NUMA系統(tǒng)是一種多處理器環(huán)境下設(shè)計(jì)的內(nèi)存結(jié)構(gòu)。在NUMA架構(gòu)出現(xiàn)前,CPU歡快的朝著頻率越來(lái)越高的方向發(fā)展。受到物理極限的挑戰(zhàn),又轉(zhuǎn)為核數(shù)越來(lái)越多的方向發(fā)展。如果每個(gè)core的工作性質(zhì)都是share-nothing(類似于map-reduce的node節(jié)點(diǎn)的作業(yè)屬性),那么也許就不會(huì)有NUMA。由于所有CPU Core都是通過(guò)共享一個(gè)北橋來(lái)讀取內(nèi)存,無(wú)論核數(shù)如何的發(fā)展,北橋在響應(yīng)時(shí)間上的性能瓶頸越來(lái)越明顯。于是,聰明的硬件設(shè)計(jì)師們,想到了把內(nèi)存控制器(原本北橋中讀取內(nèi)存的部分)也做個(gè)拆分,平分到了每個(gè)die上。于是NUMA就出現(xiàn)了!
NUMA中,雖然內(nèi)存直接attach在CPU上,但是由于內(nèi)存被平均分配在了各個(gè)die上。只有當(dāng)CPU訪問(wèn)自身直接attach內(nèi)存對(duì)應(yīng)的物理地址時(shí),才會(huì)有較短的響應(yīng)時(shí)間(后稱Local Access)。而如果需要訪問(wèn)其他CPU attach的內(nèi)存的數(shù)據(jù)時(shí),就需要通過(guò)inter-connect通道訪問(wèn),響應(yīng)時(shí)間就相比之前變慢了(后稱Remote Access)。所以NUMA(Non-Uniform Memory Access)就此得名。
NUMA的幾個(gè)概念(Node,socket,core,thread)
socket:就是主板上的CPU插槽;
Core:就是socket里獨(dú)立的一組程序執(zhí)行的硬件單元,比如寄存器,計(jì)算單元等;
Thread:就是超線程hyperthread的概念,邏輯的執(zhí)行單元,獨(dú)立的執(zhí)行上下文,但是共享core內(nèi)的寄存器和計(jì)算單元。
Node:這個(gè)概念其實(shí)是用來(lái)解決core的分組的問(wèn)題,具體參見(jiàn)下圖來(lái)理解(圖中的OS CPU可以理解thread,那么core就沒(méi)有在圖中畫出),從圖中可以看出共有4個(gè)socket,每個(gè)socket 2個(gè)node,每個(gè)node中有8個(gè)thread,總共4(Socket)× 2(Node)× 8(4core × 2 Thread) = 64個(gè)thread。另外每個(gè)node有自己的內(nèi)部CPU,總線和內(nèi)存,同時(shí)還可以訪問(wèn)其他node內(nèi)的內(nèi)存,NUMA的最大的優(yōu)勢(shì)就是可以方便的增加CPU的數(shù)量,因?yàn)镹ode內(nèi)有自己內(nèi)部總線,所以增加CPU數(shù)量可以通過(guò)增加Node的數(shù)目來(lái)實(shí)現(xiàn),如果單純的增加CPU的數(shù)量,會(huì)對(duì)總線造成很大的壓力,所以UMA結(jié)構(gòu)不可能支持很多的核。下圖出自:《NUMA Best Practices for Dell PowerEdge 12th Generation Servers》。
由于每個(gè)node內(nèi)部有自己的CPU總線和內(nèi)存,所以如果一個(gè)虛擬機(jī)的vCPU跨不同的Node的話,就會(huì)導(dǎo)致一個(gè)node中的CPU去訪問(wèn)另外一個(gè)node中的內(nèi)存的情況,這就導(dǎo)致內(nèi)存訪問(wèn)延遲的增加。在NFV環(huán)境中,對(duì)性能有比較高的要求,就非常需要同一個(gè)虛擬機(jī)的vCPU盡量被分配到同一個(gè)Node中的pCPU上,所以在OpenStack的Kilo版本及后續(xù)版本均增加了基于NUMA感知的虛擬機(jī)調(diào)度的特性。
查看服務(wù)器中NUMA拓?fù)浼軜?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
從上面報(bào)文輸出可以看出,當(dāng)前機(jī)器有2個(gè)sockets,每個(gè)sockets包含1個(gè)numa node,每個(gè)numa node中有2個(gè)cores,每個(gè)cores包含1個(gè)thread,所以總的threads數(shù)量=2(sockets)×1(node)×2(cores)×1(threads)=4.
2)通過(guò)shell腳本打印出當(dāng)前機(jī)器的socket,core和thread的數(shù)量
#!/bin/bash
# 簡(jiǎn)單打印系統(tǒng)CPU拓?fù)?/code>
# 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中有以下策略來(lái)適應(yīng)NUMA系統(tǒng):
1)Per-core memory:一個(gè)CPU上有多個(gè)核(core),per-core memory是指每個(gè)核都有屬于自己的內(nèi)存,即對(duì)于經(jīng)常訪問(wèn)的數(shù)據(jù)結(jié)構(gòu),每個(gè)核都有自己的備份。這樣做一方面是為了本地內(nèi)存的需要,另外一方面也是前面提到的Cache一致性的需要,避免多個(gè)核訪問(wèn)同一個(gè)Cache Line。
2)本地設(shè)備本地處理:即用本地的處理器、本地的內(nèi)存來(lái)處理本地的設(shè)備上產(chǎn)生的數(shù)據(jù)。如果有一個(gè)PCI設(shè)備在node0上,就用node0上的核來(lái)處理該設(shè)備,處理該設(shè)備用到的數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)緩沖區(qū)都從node0上分配。以下是一個(gè)分配本地內(nèi)存的例子:
/* allocate memory for the queue structure */ // 該例分配一個(gè)結(jié)構(gòu)體,通過(guò)傳遞socket_id,即node id獲得本地內(nèi)存,并且以Cache Line對(duì)齊。q = rte_zmalloc_socket("fm10k", sizeof(*q), RTE_CACHE_LINE_SIZE, socket_id);
CPU的親和性調(diào)度
當(dāng)前,屬于多核處理器時(shí)代,這類多核處理器自然會(huì)面對(duì)一個(gè)問(wèn)題,按照什么策略將任務(wù)線程分配到各個(gè)處理器上執(zhí)行。眾所周知,這個(gè)分配工作一般由操作系統(tǒng)完成。負(fù)載均衡當(dāng)然是比較理想的策略,按需指定的方式也是很自然的訴求,因?yàn)槠渚哂写_定性。簡(jiǎn)單地說(shuō),CPU親和性(Core affinity)就是一個(gè)特定的任務(wù)要在某個(gè)給定的CPU上盡量長(zhǎng)時(shí)間地運(yùn)行而不被遷移到其他處理器上的傾向性。這意味著線程可以不在處理器之間頻繁遷移,從而減少不必要的開(kāi)銷。
Linux內(nèi)核包含了一種機(jī)制,它讓開(kāi)發(fā)人員可以編程實(shí)現(xiàn)CPU親和性。也就是說(shuō)可以將應(yīng)用程序顯式地指定線程在哪個(gè)(或哪些)CPU上運(yùn)行。
在Linux內(nèi)核中,所有的線程都有一個(gè)相關(guān)的數(shù)據(jù)結(jié)構(gòu),稱為task_struct。這個(gè)結(jié)構(gòu)非常重要,這里不展開(kāi)討論,只討論其中與親和性相關(guān)度最高的是cpus_allowed位掩碼。這個(gè)位掩碼由n位組成,與系統(tǒng)中的n個(gè)邏輯處理器一一對(duì)應(yīng)。具有4個(gè)物理CPU的系統(tǒng)可以有4位。如果這些CPU都啟用了超線程,那么這個(gè)系統(tǒng)就有一個(gè)8位的位掩碼。
如果針對(duì)某個(gè)線程設(shè)置了指定的位,那么這個(gè)線程就可以在相關(guān)的CPU上運(yùn)行。因此,如果一個(gè)線程可以在任何CPU上運(yùn)行,并且能夠根據(jù)需要在處理器之間進(jìn)行遷移,那么位掩碼就全是1。實(shí)際上,在Linux中,這就是線程的默認(rèn)狀態(tài)。
Linux內(nèi)核API提供了一些方法,讓用戶可以修改位掩碼或查看當(dāng)前的位掩碼:
sched_set_affinity()(用來(lái)修改位掩碼)
sched_get_affinity()(用來(lái)查看當(dāng)前的位掩碼)
注意,cpu_affinity會(huì)被傳遞給子線程,因此應(yīng)該適當(dāng)?shù)卣{(diào)用sched_set_affinity。
將線程與CPU綁定,最直觀的好處就是提高了CPU Cache的命中率,從而減少內(nèi)存訪問(wèn)損耗,提高程序的速度。在多核體系CPU上,提高外設(shè)以及程序工作效率最直觀的辦法就是讓各個(gè)物理核各自負(fù)責(zé)專門的事情。尤其在在NUMA架構(gòu)下,這個(gè)操作對(duì)系統(tǒng)運(yùn)行速度的提升有更大的意義,跨NUMA節(jié)點(diǎn)的任務(wù)切換,將導(dǎo)致大量三級(jí)Cache的丟失。從這個(gè)角度來(lái)看,NUMA使用CPU綁定時(shí),每個(gè)核心可以更專注地處理一件事情,資源體系被充分使用,減少了同步的損耗。
通常Linux內(nèi)核都可以很好地對(duì)線程進(jìn)行調(diào)度,在應(yīng)該運(yùn)行的地方運(yùn)行線程,也就是說(shuō)在可用的處理器上運(yùn)行并獲得很好的整體性能。內(nèi)核包含了一些用來(lái)檢測(cè)CPU之間任務(wù)負(fù)載遷移的算法,可以啟用線程遷移來(lái)降低繁忙的處理器的壓力。只有在以下三個(gè)特殊場(chǎng)景會(huì)用到CPU親和性綁定機(jī)制:
大量計(jì)算:在科學(xué)計(jì)算和理論計(jì)算中,如果不進(jìn)行CPU親和性綁定,會(huì)發(fā)現(xiàn)自己的應(yīng)用程序要在多處理器的機(jī)器上花費(fèi)大量時(shí)間進(jìn)行遷移從而完成計(jì)算。
復(fù)雜程序測(cè)試:比如在線性可伸縮測(cè)試中,我們期望的理論模型是如果應(yīng)用程序隨著CPU的增加可以線性地伸縮,那么每秒事務(wù)數(shù)和CPU個(gè)數(shù)之間應(yīng)該會(huì)是線性的關(guān)系。這樣建模可以測(cè)試應(yīng)用程序是否可以有效地使用底層硬件。如果一個(gè)給定的線程遷移到其他地方去了,那么它就失去了利用CPU緩存的優(yōu)勢(shì)。實(shí)際上,如果正在使用的CPU需要為自己緩存一些特殊的數(shù)據(jù),那么其他所有CPU都會(huì)使這些數(shù)據(jù)在自己的緩存中失效。因此,如果有多個(gè)線程都需要相同的數(shù)據(jù),那么將這些線程綁定到一個(gè)特定的CPU上,就可以確保它們?cè)L問(wèn)相同的緩存數(shù)據(jù)或者至少可以提高緩存的命中率。
實(shí)時(shí)性線程:對(duì)于實(shí)時(shí)性線程經(jīng)常會(huì)希望使用親和性來(lái)指定一個(gè)8路主機(jī)上的某個(gè)CPU來(lái)處理,而同時(shí)允許其他7個(gè)CPU處理所有普通的系統(tǒng)調(diào)度。這種做法對(duì)長(zhǎng)時(shí)間運(yùn)行、對(duì)時(shí)間敏感的應(yīng)用程序可以確保正常運(yùn)行,同時(shí)可以允許其他應(yīng)用程序獨(dú)占其余的計(jì)算資源。
Linux內(nèi)核提供了啟動(dòng)參數(shù)isolcpus。對(duì)于有4個(gè)CPU的服務(wù)器,在啟動(dòng)的時(shí)候加入啟動(dòng)參數(shù)isolcpus=2,3。那么系統(tǒng)啟動(dòng)后將不使用CPU3和CPU4。注意,這里說(shuō)的不使用不是絕對(duì)地不使用,系統(tǒng)啟動(dòng)后仍然可以通過(guò)taskset命令指定哪些程序在這些核心中運(yùn)行。
1)修改/etc/default/grub文件中內(nèi)容,在CMDLINE中添加如下圖所示設(shè)置
2)編譯內(nèi)核啟動(dòng)文件
[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通過(guò)在多核設(shè)備上創(chuàng)建多個(gè)線程,每個(gè)線程綁定到單獨(dú)的核上,減少線程調(diào)度的開(kāi)銷,以提高性能。DPDK的線程可以作為控制線程,也可以作為數(shù)據(jù)線程??刂凭€程一般綁定到MASTER核上,接受用戶配置,并傳遞配置參數(shù)給數(shù)據(jù)線程等;數(shù)據(jù)線程分布在不同核上處理數(shù)據(jù)包。
DPDK的lcore指的是EAL線程,本質(zhì)是基于pthread(Linux/FreeBSD)封裝實(shí)現(xiàn)。Lcore(EAL pthread)由remote_launch函數(shù)指定的任務(wù)創(chuàng)建并管理。在每個(gè)EAL pthread中,有一個(gè)TLS(Thread Local Storage)稱為_(kāi)lcore_id。當(dāng)使用DPDK的EAL‘-c’參數(shù)指定coremask時(shí),EAL pthread生成相應(yīng)個(gè)數(shù)lcore,并默認(rèn)是1:1親和到coremask對(duì)應(yīng)的CPU邏輯核,_lcore_id和CPU ID是一致的。
// rte_eal_cpu_init()函數(shù)中,通過(guò)讀取/sys/devices/system/cpu/cpuX/下的相關(guān)信息,確定當(dāng)前系統(tǒng)有哪些CPU核,以及每個(gè)核屬于哪個(gè)CPU Socket。// eal_parse_args()函數(shù),解析-c參數(shù),確認(rèn)哪些CPU核是可以使用的,以及設(shè)置第一個(gè)核為MASTER。// 為每一個(gè)SLAVE核創(chuàng)建線程,并調(diào)用eal_thread_set_affinity()綁定CPU。// 線程的執(zhí)行體是eal_thread_loop(),函數(shù)內(nèi)部的主體是一個(gè)while死循環(huán),調(diào)用不同模塊注冊(cè)到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ù)注冊(cè)到lcore_config[].f中。// 以l2fwd為例,注冊(cè)的回調(diào)處理函數(shù)是l2fwd_launch_on_lcore()。rte_eal_mp_remote_launch(l2fwd_launch_one_lcore, NULL, CALL_MASTER);
DPDK每個(gè)核上的線程最終會(huì)調(diào)用eal_thread_loop()>>> l2fwd_launch_on_lcore(),調(diào)用到自己實(shí)現(xiàn)的處理函數(shù)。默認(rèn)情況下,lcore是與邏輯核一一親和綁定的。帶來(lái)性能提升的同時(shí),也犧牲了一定的靈活性和能效。在現(xiàn)網(wǎng)中,往往有流量潮汐現(xiàn)象的發(fā)生,在網(wǎng)絡(luò)流量空閑時(shí),沒(méi)有必要使用與流量繁忙時(shí)相同的核數(shù)。于是,EAL pthread和邏輯核之間進(jìn)而允許打破1:1的綁定關(guān)系,使得_lcore_id本身和CPU ID可以不嚴(yán)格一致。EAL定義了長(zhǎng)選項(xiàng)“——lcores”來(lái)指定lcore的CPU親和性。對(duì)一個(gè)特定的lcore ID或者lcore ID組,這個(gè)長(zhǎng)選項(xiàng)允許為EAL pthread設(shè)置CPU集。這個(gè)選項(xiàng)以及對(duì)應(yīng)的一組API(rte_thread_set/get_affinity())為lcore提供了親和的靈活性。lcore可以親和到一個(gè)CPU或者一個(gè)CPU集合,使得在運(yùn)行時(shí)調(diào)整具體某個(gè)CPU承載lcore成為可能。同時(shí),多個(gè)lcore也可能親和到同一個(gè)核,但是這種情況下如果調(diào)度占用的內(nèi)核庫(kù)是非搶占式,就存在鎖機(jī)制,DPDK技術(shù)棧在電信云中的最佳實(shí)踐(3)中會(huì)專門針對(duì)不同鎖進(jìn)制進(jìn)行討論。
除了使用DPDK提供的邏輯核之外,用戶也可以將DPDK的執(zhí)行上下文運(yùn)行在任何用戶自己創(chuàng)建的pthread中。在普通用戶自定義的pthread中,lcore id的值總是LCORE_ID_ANY,以此確定這個(gè)thread是一個(gè)有效的普通用戶所創(chuàng)建的pthread。用戶創(chuàng)建的pthread可以支持絕大多數(shù)DPDK庫(kù),沒(méi)有任何影響。但少數(shù)DPDK庫(kù)可能無(wú)法完全支持用戶自創(chuàng)建的pthread,如timer和Mempool。詳細(xì)請(qǐng)參見(jiàn)《DPDK開(kāi)發(fā)者手冊(cè)多線程章節(jié)》。
DPDK不僅可以通過(guò)綁核完成大量計(jì)算任務(wù)資源親和性調(diào)度,同時(shí)在計(jì)算任務(wù)較小,一個(gè)核的資源綽綽有余的情況下,還可以通過(guò)Linux的cgroup對(duì)資源進(jìn)行釋放。因?yàn)?,DPDK的線程其實(shí)就是普通的pthread,其本質(zhì)就是使用cgroup能把CPU的配額靈活地配置在不同的線程上。因此,DPDK可以借助cgroup實(shí)現(xiàn)計(jì)算資源配額對(duì)于線程的靈活配置,可以有效改善I/O核的閑置利用率。
最后,用一張圖來(lái)總結(jié)lcore的啟動(dòng)過(guò)程和執(zhí)行任務(wù)分發(fā)的流程。