ODP/DPDK代碼級性能優化總結Tips

ODP/DPDK代碼級性能優化總結Tips

以下過程基於ARM 64位CPU, 僅供參考

ODP是Linaro基金下面的開源框架,類似於DPDK。最近用ODP程序DEMO公司SOC性能,性能不理想,優化了一圈又一圈,發現驅動水分很大,包括ODP框架本身。中間不聽Architect的建議,自作主張用DPDK+ODP來展示一下我們的多樣化驅動, 找到方案,開發中發現DPDK驅動性能也不理想,自己吹的牛,含着淚也要優化完。

前提:
這裏主要用64B小包簡單反射儀表進來的數據,目的是確認驅動性能最優。進一步讀取報文內容會導致加載額外cacheline,性能會下降一些。10G網口小包線速14.88Mpps, 對於2G CPU來說134個時鐘週期,平均到每個報文的指令數是關鍵。

Perf性能檢測工具:
如果不能用ubuntu直接安裝,比如自己編譯的Kernel源碼,到tools/perf下make, 生產的perf就是了,複製到usr/bin下面去。
perf list 可以列出你當前cpu支持的性能參數
perf stat -p 'pidof your_app` -e task-clock,...用來檢測程序的性能參數
注意-p參數附加到現有進程,可以避免看到程序啓動過程的影響。
-e 中要有task-clock這樣可以看到更多的%和M/s的統計參數
如果你的cache miss rate大於5%, 輸出會有顏色標識
Perf在跟蹤cache load miss rate和write miss rate特別有用,沒有被cache到的數據訪用它能明顯看出差別。

高精度時鐘:
千萬別用系統函數來去時間評估性能,系統開銷太大。rte_rdtsc() 是個很好的實現,一條彙編指令。在我的環境下對性能影響微乎其微。用全局變量分別累計batch處理中rx/rd/tx/free時間,每個局部需要統計的代碼段的累加時間都除以這個總數,以%顯示出來,這樣一兩個指令的節省都能明顯看到%在變化。下面是我用到的幾個宏,哪段代碼懷疑有問題馬上加上去
PERF_VAR xxx; //定義 全局performance counter
PERF_START(); //用在需要統計的代碼段開頭
PERF_COUNT(xxx); //統計某計數器
PERF_DUMP(xxx, sum); //命令行調用或者exit時打印性能百分比
注意:x86下面貌似RTDSC命令非常耗時,如果對每個包做性能統計時間會非常離譜,建議對批做統計,需要統計的代碼段分開。

Eclipse:
保存時自動編譯,甚至ssh到設備kill & run,節省很多時間。性能調優需要不停的修改對比,過程自動化能節省很多時間。

Gdb:
ODP/DPDK類用戶態框架就是調試方便,這點比bare metal和kernel類程序強很多,有問題馬上用"-g -O0"編譯調試。
有空用GDB對程序逐步跟蹤一下,說不定有驚喜 :)

GIT:
曾經週末在家改了兩天code, 一次checkin, 結果因爲改了太多關鍵地方,出錯了要調試很久。後來老老實實改一點提交一點,這樣哪一步出錯很容易追溯。Git的本地分支和提交,應該好好利用起來。

Assert:
當年面試Java, 在白板上寫個簡單api, 寫完之後兩個面試官熱淚盈眶,我們終於看到會用assert的人" 。底層api編寫儘量少用if else檢查,上面要判斷返回值,整個代碼會很難看。底層調用自己給自己用,這些約束在調試時滿足就可以,調試期間通過宏使能檢查。更要命的是這些額外指令會嚴重拖累性能,特別是每個包都會調用的函數。運行期間要用宏關閉。
在優化ODP Ring/Pool操作時,很多底層函數因爲沒有assert, 程序出錯只能單步跟蹤,費時費力。

編譯:
除了-O3還要打開-mtune。有時候perf看到iTLB miss很大,用-Os會明顯減小代碼尺寸,可以嘗試。最後還是要加上-g反彙編看看結果。

Batch:
Batch處理是新網絡處理框架的精華之一,也是性能優化的關鍵。Batch要用在各個方面。比如ring填充,分配一個報文寫一個就不如一次分配一批然後批量寫進去。雖然Pool裏面用了hugepage, 又用了per core的緩存,每單次操作還是要判斷並更新指針,最少幾個指令。如果一次分批一批,平均到每個報文就一兩個而已。
CPU可以緩存數據和指令,D-cache和i-Cache, 一般d-cache可以人爲prefetch, i-cache則不行,所以對於幾十K的i-cache儘量趁熱把它利用好,batch操作完再進入下一段代碼。

Pool:
Pool還能優化。比如大家排隊打飯,每人遞過去一個容器(數組),大師傅給你裝進去。養豬的算法更高效,每頭豬分配一段空間自己吃去。也就是把內存段的位置返回,不需要再一個個複製到接收數組裏面。Pool cache是數組,快用完了再去大池子裏分配,用這種返回指針方式更高效,少一次內存複製。
dpdk缺省的pool是ring實現,cons和prod在兩個方向上,如果操作頻繁相當於要不停遍歷整個pool, cache利用不好。如果使用stack pool實現,放進來的馬上分配出去會很好的利用緩存。stack設計沒有ring的四中組合,只有一種加鎖方式。


For循環:
一般常用的For循環對簡單的循環體來說效率不高,指令都被浪費在i++和判斷上,空間換時間的算法參考:DEQUEUE_PTRS()和rte_memcpy(), ODP pool裏面更是一次switch 32個來優化內存複製

Inline和函數指針:
-O3基本上會幫你自動inline,不過最好還是objdump看看彙編,有沒有surprise. 函數指針會影響性能,比如dpdk裏面的callback, 如果不用建議在.config裏面關掉。

Likely/Unlikely:
雖然cpu分支預測已經做得很好,自己預測的分支更準。

Prefetch
記得以前寫過一個順序大內存訪問程序,步長小於cacheline的時候性能基本一樣,大於cacheline的時候性能嚴重惡化。這裏有三個個有趣因素:同一cacheline訪問時間消耗很小,因爲數據已經在L1裏面。超過一定數量的連續內存訪問後,d-cache會通過預測提前加載後序內存,性能很好。超過一定步長預測程序就傻掉。所以要提高性能,一定要減小cache miss! 用Perf經常記錄cache miss百分比。Prefetch用的不好,性能不僅沒提升,D-cache反而因擠出有用數據,不如prefetch_non_tempral。


內存對齊:
一個struct包含u64, u8, u8,那麼這個結構的數組操作會快嗎?No, 改成u64, u32, u32更快。

Struct寫:
還是上面那個結構數組,寫入前兩個字段。這也能優化?能:把不用的那個字段寫0下去。What,加了一條指令,你有病嗎?你有藥嗎?因爲寫內存是write-back,cache line(64B)讀上來,合併再回寫內存,如果全覆蓋就不用讀了吧。PS, 俺家丫頭生病的時候就可以理直氣壯的問:這回知道誰有病了吧?我有藥哦。。。

寄存器變量:
一些經常讀寫的內存可以存放到register變量,比L1還快。。
記得ARM有16B寄存器,一直沒試試在賦值清零的時候會不會節省指令。

if分支:
能少儘量少,特別是主分支上

mbuf字段順序:
meta裏面有128B, 兩個cacheline大小,把收發包常用的字段的集中到前面,只操作一個CL。perf觀測到cache r/w miss rate明顯下降。

指針:
64位系統裏面的8B內存指針真是浪費,因爲現在都是hugepage, 很多地址都是連續的,特別是pool裏面,所以mbuf可以用idx來替代,而且可以很短。一般網卡的回送數據用來查找對應的mbuf, 短地址就可以減少一次內存查找。

Pool cache size:
這是每個core專有的cache, 訪問快,容量不夠要去大池訪問,所以大小要能容納常用存取,儘量不要touch大池子。
Rx/tx隊列不能貪多,夠用就好否則超出Cache能力反而降低性能

少用內存:
特別是per packet的內存,能cache也不好,有些數據可以合併到mbuf裏面,或者作爲網卡的回送數據。上面提到短索引替代64位指針,如果用在mbuf裏面可以節省很多字節。
這裏有個很好的文章關於內存訪問時間:[url=http://www.cnblogs.com/xkfz007/archive/2012/10/08/2715163.html]CPU與內存的那些事[/url]

CPU利用率統計:
有個簡單辦法,沒收到報文時進入一個空轉,指令數和普通報文處理差不多。否則性能統計上看RX會佔用很多時間。還要統計一下Batch沒填滿的比例,能大概看出負載狀況。

DPDK配置:
這些是gdb單步出來的 :) 如果你的應用沒那麼複雜可以用spsc, 或者手工調用api
CONFIG_RTE_MBUF_DEFAULT_MEMPOOL_OPS="ring_sp_sc"
CONFIG_RTE_MBUF_REFCNT_ATOMIC=n
CONFIG_RTE_PKTMBUF_HEADROOM=0?
CONFIG_RTE_ETHDEV_RXTX_CALLBACKS=n


參考鏈接(剛找到的,應該早點搜搜看):
http://dpdk.org/doc/guides/prog_guide/writing_efficient_code.html
http://events.linuxfoundation.org/sites/events/files/slides/DPDK-Performance.pdf
https://software.intel.com/en-us/articles/dpdk-performance-optimization-guidelines-white-paper

結果:
經過優化的ODP/DPDK在ARM SOC上達到線速,但是即使只讀報文中的一個字節,因爲加載了一個CL, 性能還是會降低一些,但是比沒優化之前還是提高了很多。X86本身比較彪悍,公司網卡沒優化也能跑到線速,看不出差別,後面會找25G雙口網卡測試

功耗:
這種PMD模式框架本質上是個死循環,不知道能不能在沒收到數據的時候進入WFI/WFE, 等待硬件喚醒

展望:
Intel開源做的很好,代碼精煉,越來越多的公司在用DPDK做逐漸複雜的事情,大多數還在網絡應用層面。個人覺得應該把這種編程模型和性能壓縮思想上升到應用層面,比如數據庫、存儲、Web、再成熟就進FPGA,最終固化爲ASIC,成本應該逐數量級降低。
注意:mtcp+dpdk的應用不應該侷限爲模擬socket接口,用DPDK裏面的批量處理思想,對上層應用包括web server做批量化改造. 剛看到fd.io的TLDK, intel參與的開源udp/tcp框架

DPDK性能優化沒有那麼神祕,分享一下筆記,歡迎交流經驗:[email protected] 微信號: steeven_li
[img]http://dl2.iteye.com/upload/attachment/0122/1826/8f43158e-e1db-3a0a-91a9-201268a9d5eb.png[/img]


2016平安夜

12/28補充:X86下的性能結果也出來了,about 50% improvement! 同時也看到x86 cpu性能確實比ARM至少好處兩倍以上。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章