一、網絡IO的處境和趨勢 從我們用戶的使用就可以感受到網速一直在提升,而網絡技術的發(fā)展也從1GE/10GE/25GE /40GE/100GE的演變,從中可以得出單機的網絡IO能力必須跟上時代的發(fā)展。
傳統(tǒng)的電信領域
IP層及以下,例如路由器、交換機、防火墻、基站等設備都是采用硬件解決方案。基于專用網絡處理器(NP),有基于FPGA,更有基于ASIC的。但是基于硬件的劣勢非常明顯,發(fā)生Bug不易修復,不易調試維護,并且網絡技術一直在發(fā)展,例如2G/3G/4G/5G等移動技術的革新,這些屬于業(yè)務的邏輯基于硬件實現太痛苦,不能快速迭代。傳統(tǒng)領域面臨的挑戰(zhàn)是急需一套軟件架構的高性能網絡IO開發(fā)框架。
云的發(fā)展
私有云的出現通過網絡功能虛擬化(NFV)共享硬件成為趨勢,NFV的定義是通過標準的服務器、標準交換機實現各種傳統(tǒng)的或新的網絡功能。急需一套基于常用系統(tǒng)和標準服務器的高性能網絡IO開發(fā)框架。
單機性能的飆升
網卡從1G到100G的發(fā)展,CPU從單核到多核到多CPU的發(fā)展,服務器的單機能力通過橫行擴展達到新的高點。但是軟件開發(fā)卻無法跟上節(jié)奏,單機處理能力沒能和硬件門當戶對,如何開發(fā)出與時并進高吞吐量的服務,單機百萬千萬并發(fā)能力。即使有業(yè)務對QPS要求不高,主要是CPU密集型,但是現在大數據分析、人工智能等應用都需要在分布式服務器之間傳輸大量數據完成作業(yè)。這點應該是我們互聯網后臺開發(fā)最應關注,也最關聯的。
二、Linux + x86網絡IO瓶頸
在數年前曾經寫過《網卡工作原理及高并發(fā)下的調優(yōu)》一文,描述了Linux的收發(fā)報文流程。根據經驗,在C1(8核)上跑應用每1W包處理需要消耗1%軟中斷CPU,這意味著單機的上限是100萬PPS(Packet Per Second)。從TGW(Netfilter版)的性能100萬PPS,AliLVS優(yōu)化了也只到150萬PPS,并且他們使用的服務器的配置還是比較好的。假設,我們要跑滿10GE網卡,每個包64字節(jié),這就需要2000萬PPS(注:以太網萬兆網卡速度上限是1488萬PPS,因為最小幀大小為84B《Bandwidth, Packets Per Second, and Other Network Performance Metrics》),100G是2億PPS,即每個包的處理耗時不能超過50納秒。而一次Cache Miss,不管是TLB、數據Cache、指令Cache發(fā)生Miss,回內存讀取大約65納秒,NUMA體系下跨Node通訊大約40納秒。所以,即使不加上業(yè)務邏輯,即使純收發(fā)包都如此艱難。我們要控制Cache的命中率,我們要了解計算機體系結構,不能發(fā)生跨Node通訊。 從這些數據,我希望可以直接感受一下這里的挑戰(zhàn)有多大,理想和現實,我們需要從中平衡。問題都有這些
傳統(tǒng)的收發(fā)報文方式都必須采用硬中斷來做通訊,每次硬中斷大約消耗100微秒,這還不算因為終止上下文所帶來的Cache Miss。
數據必須從內核態(tài)用戶態(tài)之間切換拷貝帶來大量CPU消耗,全局鎖競爭。
收發(fā)包都有系統(tǒng)調用的開銷。
內核工作在多核上,為可全局一致,即使采用Lock Free,也避免不了鎖總線、內存屏障帶來的性能損耗。
從網卡到業(yè)務進程,經過的路徑太長,有些其實未必要的,例如netfilter框架,這些都帶來一定的消耗,而且容易Cache Miss。
三、DPDK的基本原理
從前面的分析可以得知IO實現的方式、內核的瓶頸,以及數據流過內核存在不可控因素,這些都是在內核中實現,內核是導致瓶頸的原因所在,要解決問題需要繞過內核。所以主流解決方案都是旁路網卡IO,繞過內核直接在用戶態(tài)收發(fā)包來解決內核的瓶頸。 Linux社區(qū)也提供了旁路機制Netmap,官方數據10G網卡1400萬PPS,但是Netmap沒廣泛使用。其原因有幾個:
Netmap需要驅動的支持,即需要網卡廠商認可這個方案。
Netmap仍然依賴中斷通知機制,沒完全解決瓶頸。
Netmap更像是幾個系統(tǒng)調用,實現用戶態(tài)直接收發(fā)包,功能太過原始,沒形成依賴的網絡開發(fā)框架,社區(qū)不完善。
那么,我們來看看發(fā)展了十幾年的DPDK,從Intel主導開發(fā),到華為、思科、AWS等大廠商的加入,核心玩家都在該圈子里,擁有完善的社區(qū),生態(tài)形成閉環(huán)。早期,主要是傳統(tǒng)電信領域3層以下的應用,如華為、中國電信、中國移動都是其早期使用者,交換機、路由器、網關是主要應用場景。但是,隨著上層業(yè)務的需求以及DPDK的完善,在更高的應用也在逐步出現。 DPDK旁路原理:?
? 左邊是原來的方式數據從 網卡 -> 驅動 -> 協議棧 -> Socket接口 -> 業(yè)務 右邊是DPDK的方式,基于UIO(Userspace I/O)旁路數據。數據從 網卡 -> DPDK輪詢模式-> DPDK基礎庫 -> 業(yè)務 用戶態(tài)的好處是易用開發(fā)和維護,靈活性好。并且Crash也不影響內核運行,魯棒性強。 DPDK支持的CPU體系架構:x86、ARM、PowerPC(PPC) DPDK支持的網卡列表:https://core.dpdk.org/supported/,我們主流使用Intel 82599(光口)、Intel x540(電口)
四、DPDK的基石UIO
為了讓驅動運行在用戶態(tài),Linux提供UIO機制。使用UIO可以通過read感知中斷,通過mmap實現和網卡的通訊。 UIO原理:?
要開發(fā)用戶態(tài)驅動有幾個步驟:
開發(fā)運行在內核的UIO模塊,因為硬中斷只能在內核處理
通過/dev/uioX讀取中斷
通過mmap和外設共享內存
五、DPDK核心優(yōu)化:PMD
DPDK的UIO驅動屏蔽了硬件發(fā)出中斷,然后在用戶態(tài)采用主動輪詢的方式,這種模式被稱為PMD(Poll Mode Driver)。 UIO旁路了內核,主動輪詢去掉硬中斷,DPDK從而可以在用戶態(tài)做收發(fā)包處理。帶來Zero Copy、無系統(tǒng)調用的好處,同步處理減少上下文切換帶來的Cache Miss。 運行在PMD的Core會處于用戶態(tài)CPU100%的狀態(tài)?
網絡空閑時CPU長期空轉,會帶來能耗問題。所以,DPDK推出Interrupt DPDK模式。 Interrupt DPDK:?
? 它的原理和NAPI很像,就是沒包可處理時進入睡眠,改為中斷通知。并且可以和其他進程共享同個CPU Core,但是DPDK進程會有更高調度優(yōu)先級。
六、DPDK的高性能代碼實現
采用HugePage減少TLB Miss
默認下Linux采用4KB為一頁,頁越小內存越大,頁表的開銷越大,頁表的內存占用也越大。CPU有TLB(Translation Lookaside Buffer)成本高所以一般就只能存放幾百到上千個頁表項。如果進程要使用64G內存,則64G/4KB=16000000(一千六百萬)頁,每頁在頁表項中占用16000000 * 4B=62MB。如果用HugePage采用2MB作為一頁,只需64G/2MB=2000,數量不在同個級別。 而DPDK采用HugePage,在x86-64下支持2MB、1GB的頁大小,幾何級的降低了頁表項的大小,從而減少TLB-Miss。并提供了內存池(Mempool)、MBuf、無鎖環(huán)(Ring)、Bitmap等基礎庫。根據我們的實踐,在數據平面(Data Plane)頻繁的內存分配釋放,必須使用內存池,不能直接使用rte_malloc,DPDK的內存分配實現非常簡陋,不如ptmalloc。
SNA(Shared-nothing Architecture)
軟件架構去中心化,盡量避免全局共享,帶來全局競爭,失去橫向擴展的能力。NUMA體系下不跨Node遠程使用內存。
SIMD(Single Instruction Multiple Data)
從最早的mmx/sse到最新的avx2,SIMD的能力一直在增強。DPDK采用批量同時處理多個包,再用向量編程,一個周期內對所有包進行處理。比如,memcpy就使用SIMD來提高速度。 SIMD在游戲后臺比較常見,但是其他業(yè)務如果有類似批量處理的場景,要提高性能,也可看看能否滿足。
不使用慢速API
這里需要重新定義一下慢速API,比如說gettimeofday,雖然在64位下通過vDSO已經不需要陷入內核態(tài),只是一個純內存訪問,每秒也能達到幾千萬的級別。但是,不要忘記了我們在10GE下,每秒的處理能力就要達到幾千萬。所以即使是gettimeofday也屬于慢速API。DPDK提供Cycles接口,例如rte_get_tsc_cycles接口,基于HPET或TSC實現。
5. 編譯執(zhí)行優(yōu)化
分支預測
現代CPU通過pipeline、superscalar提高并行處理能力,為了進一步發(fā)揮并行能力會做分支預測,提升CPU的并行能力。遇到分支時判斷可能進入哪個分支,提前處理該分支的代碼,預先做指令讀取編碼讀取寄存器等,預測失敗則預處理全部丟棄。我們開發(fā)業(yè)務有時候會非常清楚這個分支是true還是false,那就可以通過人工干預生成更緊湊的代碼提示CPU分支預測成功率。
CPU Cache預取
Cache Miss的代價非常高,回內存讀需要65納秒,可以將即將訪問的數據主動推送的CPU Cache進行優(yōu)化。比較典型的場景是鏈表的遍歷,鏈表的下一節(jié)點都是隨機內存地址,所以CPU肯定是無法自動預加載的。但是我們在處理本節(jié)點時,可以通過CPU指令將下一個節(jié)點推送到Cache里。 API文檔:https://doc.dpdk.org/api/rte__prefetch_8h.html
內存對齊
內存對齊有2個好處: 避免結構體成員跨Cache Line,需2次讀取才能合并到寄存器中,降低性能。結構體成員需從大到小排序和以及強制對齊。參考《Data alignment: Straighten up and fly right》 多線程場景下寫產生False sharing,造成Cache Miss,結構體按Cache Line對齊。
常量優(yōu)化
常量相關的運算的編譯階段完成。比如C++11引入了constexp,比如可以使用GCC的__builtin_constant_p來判斷值是否常量,然后對常量進行編譯時得出結果。 ? ? 5. 使用CPU指令 現代CPU提供很多指令可直接完成常見功能,比如大小端轉換,x86有bswap指令直接支持了。 這個實現,也是GLIBC的實現,先常量優(yōu)化、CPU指令優(yōu)化、最后才用裸代碼實現。畢竟都是頂端程序員,對語言、編譯器,對實現的追求不一樣,所以造輪子前一定要先了解好輪子。 Google開源的cpu_features可以獲取當前CPU支持什么特性,從而對特定CPU進行執(zhí)行優(yōu)化。高性能編程永無止境,對硬件、內核、編譯器、開發(fā)語言的理解要深入且與時俱進。
七、DPDK生態(tài)
對我們互聯網后臺開發(fā)來說DPDK框架本身提供的能力還是比較裸的,比如要使用DPDK就必須實現ARP、IP層這些基礎功能,有一定上手難度。如果要更高層的業(yè)務使用,還需要用戶態(tài)的傳輸協議支持。不建議直接使用DPDK。 目前生態(tài)完善,社區(qū)強大(一線大廠支持)的應用層開發(fā)項目是FD.io(The Fast Data Project),有思科開源支持的VPP,比較完善的協議支持,ARP、VLAN、Multipath、IPv4/v6、MPLS等。用戶態(tài)傳輸協議UDP/TCP有TLDK。從項目定位到社區(qū)支持力度算比較靠譜的框架。 Seastar也很強大和靈活,內核態(tài)和DPDK都隨意切換,也有自己的傳輸協議Seastar Native TCP/IP Stack支持,但是目前還未看到有大型項目在使用Seastar,可能需要填的坑比較多。 更多內容參考“中國電信DPDK技術白皮書v1.0、DPDK基礎—認識DPDK技術、DPDK架構高清版、DPDK編程指南(中文版)”。 ?
編輯:黃飛
?
評論