谈谈dpdk应用层包处理程序的多进程和多线程模型选择时的若干考虑
看到知乎上有個關(guān)于linux多進(jìn)程、多線程的討論:http://www.zhihu.com/question/19903801/answer/14842584
自己項(xiàng)目里也對這個問題有過很多探討和測試,所以正好開貼整理一下,題目有點(diǎn)長,其實(shí)就2點(diǎn):
1. 多進(jìn)程模型和多線程模型,這兩種模型在linux上有什么區(qū)別,各有何優(yōu)缺點(diǎn)?
? ? 這里僅限于linux平臺,因?yàn)閘inux平臺跟win平臺關(guān)于線程的實(shí)現(xiàn)差異很大。
2. 采用intel dpdk做包處理程序,是采用多進(jìn)程模型好,還是多線程模型好?
這里僅限于包處理程序(ips,waf,其他網(wǎng)絡(luò)設(shè)備引擎),因?yàn)椴煌瑧?yīng)用場景區(qū)別也很大。
?
首先知乎里邊的評論,有個miao網(wǎng)友說的跟我的經(jīng)驗(yàn)比較相符,先將其說法貼一下:
"linux使用的1:1的線程模型,在內(nèi)核中是不區(qū)分線程和進(jìn)程的,都是可運(yùn)行的任務(wù)而已。fork調(diào)用clone(最少的共享),pthread_create也是調(diào)用clone(最大共享).fork創(chuàng)建會比pthread_create多消耗一點(diǎn)點(diǎn),因?yàn)橐截恡ables和cow mapping.但是其實(shí)差別真的很細(xì)微,這些在內(nèi)核開發(fā)者的努力下已經(jīng)變的很小了。
再來說說contex switch的cost吧。線程的context switch是要比process小一些,因?yàn)榫€程共享了大部分的memory和tables,當(dāng)switch的時候這些東西已經(jīng)在緩存中了。
但是其實(shí)差別也很細(xì)微。但是在multiprocessor的系統(tǒng)中不共享memory其實(shí)是會比共享memory要有一點(diǎn)優(yōu)勢的,因?yàn)楫?dāng)任務(wù)在不同的processor中運(yùn)行的時候,同步memory帶來的損耗是不可忽視的。"
?
他這里說了兩點(diǎn)有價值的信息,1 ?linux里的線程實(shí)現(xiàn)決定,創(chuàng)建、調(diào)度、切換線程的開銷跟進(jìn)程相比,好不了多少。
2 多核CPU下由于緩存命中率的問題,進(jìn)程這種天生不共享內(nèi)存的做法,實(shí)際上比線程這種天生共享內(nèi)存
的做法,從性能上是有好處的。
這兩點(diǎn)見解跟我們項(xiàng)目實(shí)際測試和研究結(jié)果是相符合的。下面從幾個方面探討這些問題:
?
1 linux 線程創(chuàng)建方式
linux提供的線程實(shí)際上是核外線程,即主要的線程機(jī)制是通過應(yīng)用層面的庫pthread提供的(線程的id分配、線程創(chuàng)建和管理,據(jù)說基本實(shí)現(xiàn)是pthread庫為每一個進(jìn)程維護(hù)一個管理線程,單調(diào)用 pthread_create等posix API時,調(diào)用者與該管理線程通過管道傳遞命令),
核內(nèi)層面,線程幾乎可以等同于進(jìn)程。 ?這里貼一段從引用1?拷貝的內(nèi)容:
Linux的線程實(shí)現(xiàn)是在核外進(jìn)行的,核內(nèi)提供的是創(chuàng)建進(jìn)程的接口do_fork()。內(nèi)核提供了兩個系統(tǒng)調(diào)用__clone()和fork(),最終都用不同的參數(shù)調(diào)用do_fork()核內(nèi)API。 do_fork() 提供了很多參數(shù),包括CLONE_VM(共享內(nèi)存空間)、CLONE_FS(共享文件系統(tǒng)信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信號句柄表)和CLONE_PID(共享進(jìn)程ID,僅對核內(nèi)進(jìn)程,即0號進(jìn)程有效)。當(dāng)使用fork系統(tǒng)調(diào)用產(chǎn)生多進(jìn)程時,內(nèi)核調(diào)用do_fork()不使用任何共享屬性,進(jìn)程擁有獨(dú)立的運(yùn)行環(huán)境。當(dāng)使用pthread_create()來創(chuàng)建線程時,則最終設(shè)置了所有這些屬性來調(diào)用__clone(),而這些參數(shù)又全部傳給核內(nèi)的do_fork(),從而創(chuàng)建的”進(jìn)程”擁有共享的運(yùn)行環(huán)境,只有棧是獨(dú)立的,由 __clone()傳入。
?????????即:Linux下不管是多線程編程還是多進(jìn)程編程,最終都是用do_fork實(shí)現(xiàn)的多進(jìn)程編程,只是進(jìn)程創(chuàng)建時的參數(shù)不同,從而導(dǎo)致有不同的共享環(huán)境。Linux線程在核內(nèi)是以輕量級進(jìn)程的形式存在的,擁有獨(dú)立的進(jìn)程表項(xiàng),而所有的創(chuàng)建、同步、刪除等操作都在核外pthread庫中進(jìn)行。pthread 庫使用一個管理線程(__pthread_manager() ,每個進(jìn)程獨(dú)立且唯一)來管理線程的創(chuàng)建和終止,為線程分配線程ID,發(fā)送線程相關(guān)的信號,而主線程pthread_create()) 的調(diào)用者則通過管道將請求信息傳給管理線程。
上述內(nèi)容基本可以這么表示:
創(chuàng)建進(jìn)程= fork ——> do_fork(不使用共享屬性)
? ? ??創(chuàng)建線程= pthread_create——>__clone ——> do_fork(共享地址空間(代碼區(qū)、數(shù)據(jù)區(qū))、頁表、文件描述符、信號。。)
這里其實(shí)另外一種多進(jìn)程創(chuàng)建方式,就是腳本直接啟動多個進(jìn)程
?
下面再貼一段:
“對于一個進(jìn)程來說必須有的數(shù)據(jù)段、代碼段、堆棧段是不是全盤復(fù)制呢?對于多進(jìn)程來說,代碼段是肯定不用復(fù)制的,因?yàn)楦高M(jìn)程和各子進(jìn)程的代碼段是相同的,數(shù)據(jù)段和堆棧段呢?也不一定,因?yàn)樵贚inux里廣泛使用的一個技術(shù)叫copy-on-write,即寫時拷貝。copy-on-write意味著什么呢?意味著資源節(jié)省,假設(shè)有一個變量x在父進(jìn)程里存在,當(dāng)這個父進(jìn)程創(chuàng)建一個子進(jìn)程或多個子進(jìn)程時這個變量x是否復(fù)制到了子進(jìn)程的內(nèi)存空間呢?不會的,子進(jìn)程和父進(jìn)程使用同一個內(nèi)存空間的變量,但當(dāng)子進(jìn)程或父進(jìn)程要改變變量x的值時就會復(fù)制該變量,從而導(dǎo)致父子進(jìn)程里的變量值不同。”
這里我的理解是,剛fork完,子進(jìn)程和父進(jìn)程代碼段、頁表等還是共享的,接下去有兩種可能發(fā)展方向,1是子進(jìn)程修改了數(shù)據(jù),這時候,代碼段:仍然是共享的,不需要拷貝;堆和靜態(tài)數(shù)據(jù)區(qū): 根據(jù)copy-on-wirte機(jī)制,不改變值的地方仍然共享,改變值的地方需要重新申請物理頁面并修改值,修改頁表(可能還要拷貝頁表);棧:?不管進(jìn)程還是線程,都不能共享,都需要創(chuàng)建的時候分配棧區(qū)。2是fork之后馬上調(diào)用 exec 用新的進(jìn)程替換,這時候會載入新的代碼段、數(shù)據(jù)段,構(gòu)建新的頁表。
對于我們的包處理系統(tǒng)而已,無論怎么啟動,創(chuàng)建時的性能開銷其實(shí)是無所謂的,因?yàn)槎际窃谙到y(tǒng)初始化的時候創(chuàng)建。
?
2 調(diào)度和切換
由于核內(nèi)的線程本質(zhì)就是進(jìn)程,其調(diào)度過程跟進(jìn)程一樣。切換,不論是進(jìn)程切換還是線程切換,都需要替換運(yùn)行環(huán)境(內(nèi)核堆棧,運(yùn)行時寄存器等),對于內(nèi)存的切換,內(nèi)核部分內(nèi)存是一樣的,用戶空間部分:如果是進(jìn)程,需要替換頁目錄基址寄存器,如果是線程,不需要替換;總體而言,linux進(jìn)程和線程的切換,從內(nèi)存寄存器、內(nèi)核堆棧寄存器、其他寄存器等的換值開銷應(yīng)該是差不多的。具體切換代碼參考引用2
但是由于多線程共享地址空間,從一個線程切換到同一個進(jìn)程上另一個線程運(yùn)行,頁表,數(shù)據(jù)區(qū)等很多都已經(jīng)在內(nèi)存甚至緩存里,而從一個進(jìn)程切換到另一個進(jìn)程,可能由于剛切換進(jìn)來的進(jìn)程的頁面被虛擬內(nèi)存管理模塊替換出去導(dǎo)致的頁面替換開銷,另外還有緩存tlb失效導(dǎo)致的緩存更新開銷,這里性能有所差別。
?對于我們的包處理系統(tǒng)而已,采用多核架構(gòu),主體進(jìn)程/線程是綁定到不同的物理CPU core上并獨(dú)占的,所以發(fā)生調(diào)度和切換的情況不多,因而這種影響不是很重要。
3. 地址空間共享相關(guān)問題
進(jìn)程地址空間是獨(dú)立的,這意味著,不同進(jìn)程的內(nèi)存天生就是不共享的,如果要共享,則需要開發(fā)者自己構(gòu)建共享機(jī)制,比如使用IPC。
線程地址空間是共享的,這意味著,同一進(jìn)程不同線程的內(nèi)存天生是共享的,如果想要不共享,需要開發(fā)者自己實(shí)施,比如使用線程本地變量。
進(jìn)程模型和線程模型,地址空間不共享和共享,會引發(fā)以下系列問題:
3.1 進(jìn)程模型更安全、更健壯、更容易開發(fā)
由于一般公司成熟產(chǎn)品不是從無到有一個項(xiàng)目就開發(fā)完畢,必然有很多歷史代碼、多項(xiàng)目組合作的代碼,這時候采用多進(jìn)程模型,
可以有效隔離歷史代碼和當(dāng)下代碼、不同項(xiàng)目組的代碼,當(dāng)然,這需要產(chǎn)品本身是可以這么做的。比如,項(xiàng)目組A開發(fā)包處理進(jìn)程,
項(xiàng)目組B開發(fā)包安全檢測功能,兩個功能是兩個進(jìn)程,這種模型無疑更容易開發(fā)和維護(hù)
另外,由于天生所以變量都不共享,對開發(fā)者要求也比開發(fā)多線程要低
3.2 多核下的性能
傳統(tǒng)意義上,一般認(rèn)為多線程比多進(jìn)程性能要高,這其實(shí)是有前提的。比如不同線程之間需要頻繁交互大量數(shù)據(jù),由于IPC本身的開銷,
如果數(shù)據(jù)交互非常頻繁且量大,多線程會比多進(jìn)程性能要高。
對于基于DPDK的多核數(shù)據(jù)包處理程序而言,由于3個原因,多進(jìn)程模型更可預(yù)見性能高于多線程:
a DPDK提供了基于hugepage的共享內(nèi)存機(jī)制,使得多進(jìn)程物理地址相同,其虛擬地址也相同,這事實(shí)上就跟多線程之間共享地址空間是
一樣的了。即采用DPDK的基礎(chǔ)庫,多進(jìn)程之間不需要共享部分使用普通內(nèi)存(libc malloc,靜態(tài)區(qū),棧區(qū)),相互隔離很安全。需要共享
部分采用dpdk hugepage 內(nèi)存,通過特殊映射,也能共享虛擬地址。在這片共享內(nèi)存上交互數(shù)據(jù)和指針(虛擬地址是一樣的),性能
遠(yuǎn)高于利用內(nèi)核的IPC機(jī)制。
b 多核緩存?zhèn)喂蚕韱栴}
這個問題在之前帖子里http://www.cnblogs.com/jiayy/p/3246133.html說過,多核架構(gòu)一般有3層緩存,緩存命中率是系統(tǒng)整體性能最關(guān)鍵是因素之一。緩存命中率有一個致命殺手就是
偽共享現(xiàn)象,多線程由于天生所有內(nèi)存全部是共享的,所以更容易發(fā)生偽共享現(xiàn)象,其任何變量,只要一個CPU核改了,其余CPU核都產(chǎn)生
一次緩存失效并重新加載。。,而多進(jìn)程模型,共享部分是有限的且開發(fā)者可以精確設(shè)計(jì)和控制的,其偽共享現(xiàn)象可以得到有效控制。
在項(xiàng)目實(shí)際開發(fā)中,經(jīng)常的情況就是多線程性能低于多進(jìn)程,需要將很大變量改為線程局部變量,才能讓性能有所提升。
c 同步互斥
其實(shí),無論是多線程還是多進(jìn)程,都需要面臨同步和互斥,這個不是進(jìn)程/線程模型決定的,而是業(yè)務(wù)模型決定的。dpdk 提供了應(yīng)用層
空間實(shí)現(xiàn)的基礎(chǔ)互斥同步接口,包括原子操作、自旋鎖、讀寫鎖等,主要是配合共享內(nèi)存的訪問,因?yàn)閺臄?shù)據(jù)包處理系統(tǒng)來說,基本上
沒有阻塞的概念,所以這種原子操作和忙等待的鎖可以滿足大部分需求,對于需要阻塞的系統(tǒng),比如應(yīng)用層協(xié)議棧,則還是需要使用內(nèi)核的
機(jī)制,比如信號量等
4 最終采用的模型
最終我們采用的模型是:主體框架是多進(jìn)程,主進(jìn)程內(nèi)部有若干線程用于處理諸如命令接收、文件監(jiān)控、配置同步、統(tǒng)計(jì)數(shù)據(jù)寫出、
debug數(shù)據(jù)寫出等功能,包處理的主體流程是多進(jìn)程的,不同進(jìn)程之間基礎(chǔ)表項(xiàng)、數(shù)據(jù)包等數(shù)據(jù)采用dpdk共享內(nèi)存,在系統(tǒng)啟動時
靜態(tài)映射好,這些關(guān)鍵的基礎(chǔ)表項(xiàng)和數(shù)據(jù)包結(jié)構(gòu)針對緩存做細(xì)致優(yōu)化,比如對齊內(nèi)存以避免發(fā)生偽共享。由于我們的業(yè)務(wù)同步和互斥方面
的要求不多,所以只使用了有限的忙等待的鎖和原子操作函數(shù)。這種模型實(shí)際上也是intel 推薦的模型。當(dāng)然,選擇多進(jìn)程模型后,
又有很多需要考慮的東西了,比如是流水線的worker1-worker2-worker3的多進(jìn)程,還是 master-worker-worker-worker的對稱多進(jìn)程,這里頭根據(jù)業(yè)務(wù)邏輯、同步互斥、性能、擴(kuò)展性、可維護(hù)性有很多深入的考慮,這里就不詳細(xì)說了。
1?http://www.soft-bin.com/html/2010/07/09/%E5%A4%9A%E8%BF%9B%E7%A8%8Bvs%E5%A4%9A%E7%BA%BF%E7%A8%8B%EF%BC%8C%E4%B8%80%E4%B8%AA%E9%95%BF%E6%9C%9F%E7%9A%84%E4%BA%89%E8%AE%BA.html
2?http://blog.sina.com.cn/s/blog_d9889c5b0101e7x6.html
轉(zhuǎn)載于:https://blog.51cto.com/chenpiaoping/1652115
總結(jié)
以上是生活随笔為你收集整理的谈谈dpdk应用层包处理程序的多进程和多线程模型选择时的若干考虑的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dedecms /include/upl
- 下一篇: lua metatable 和 _ind