天天看點

基于DPDK(x86平台)應用性能優化實踐

産生性能瓶頸有多方面的原因,包括硬體(自身能力限制或BIOS設定不當)、作業系統(某些feature沒打開)和軟體。軟體方面的性能瓶頸主要是由于編碼不當導緻,常見原因有以下幾種:

  • 資料結構cache line未對齊,通路的資料跨cache line
  • 不恰當的記憶體操作
  • cache miss嚴重,經常跨socket通路資料
  • 鎖競争
  • 中斷太多
  • context切換頻繁

本文講述了在編碼時如何利用x86平台的特點(主要是記憶體方面)來避免性能瓶頸的技巧,并對性能優化給出一種思路。

UMA與NUMA

UMA(Uniform Memory Access),即統一記憶體通路。在UMA記憶體架構中,所有處理器通過一條總線共享記憶體,如下圖所示:

基于DPDK(x86平台)應用性能優化實踐

因為UMA結構中,所有處理器均通過同一條總線通路記憶體,故通路記憶體所花時間是一樣的。且通路時間與資料在記憶體中的位置無關。而在NUMA(Non Uniform Memory Access,非一緻性記憶體通路)結構中,通路記憶體所需時間與資料在記憶體中的位置有關,每個處理器都有自己的本地記憶體,通路本地記憶體速度很快,通過共享總線可以通路其他處理器的本地記憶體。結構如圖所示:

基于DPDK(x86平台)應用性能優化實踐

正如前面所說,在NUMA結構中,通路記憶體所需時間與資料在記憶體中的位置有很大關系。處理器通路自己的本地記憶體要比通路其他處理器的本地記憶體要快得多。DPDK支援NUMA架構,接下來主要介紹一些進行記憶體操作方面需要注意的地方。

減少記憶體拷貝

出于性能考慮,要最小化資料的記憶體拷貝。在寫代碼的時候,當遇見需要拷貝資料時,考慮有沒有一種更好的解決方式替代,如傳遞指針而非整個資料結構;在需要使用strcpy和memcpy時,用rte_strcpy和rte_memcpy作替。

合理配置設定記憶體

在實時處理資料包轉發的系統中,一般不建議在資料面進行動态記憶體配置設定,因為不停的申請和釋放動态記憶體會使堆産生碎片,管理這樣的堆開銷很大。如果真的需要在程式中動态申請記憶體,要避免使用libc的malloc接口,使用DPDK提供的類malloc函數作為替代。DPDK主要提供三種記憶體模型:rte_malloc、rte_mempool、rte_memzone,它們的使用場景如下:

  • 需要使用malloc時,用rte_malloc
  • 需要高性能配置設定記憶體時,用rte_mempool
  • 當程式需要配置設定一大塊記憶體,在程式的生命周期不釋放,用rte_memzone

關于記憶體申請,通常的做法是在程式初始化階段配置設定好固定大小記憶體,通過指針連結清單串連起來管理它的配置設定與回收。例如,NAT中的分片處理,當後續分片先于首片到達裝置時需要先緩存起來,每次從連結清單頭取出一個結點來緩存封包,當緩存封包處理完後,又把該結點“回收”到連結清單頭部。

盡量通路本地記憶體

基于DPDK(x86平台)應用性能優化實踐

根據前面的介紹,在NUMA系統中,通路本地記憶體比通路遠端記憶體更佳高效。如上圖所示,一個運作在core0上的應用通路數字标号處資料的速度快慢從高到低為1 > 2 > 3 > 4。在程式運作時,要避免進行過多的遠端記憶體通路,DPDK提供在指定socket上配置設定memory的API。

如果記憶體充裕的話,可以考慮複制一份資料到另一個socket上來提升資料讀取的速度。

資料結構設計

  • 成員變量從大到小排列,避免過多的padding。考慮如下兩個結構體:
struct s1
{
    int a;
    char b;
    char c;
};

struct s2
{   
    char b;
    int a;
    char c;
};           

複制

結構體s1的大小為8位元組,結構體s2為12位元組,在定義時不考慮padding的話,每個結構體變量會浪費4位元組。

  • 避免資料結構跨cache line,一條cache line應當正好放下整數個(N≥1)對象。
基于DPDK(x86平台)應用性能優化實踐

紅色辨別的obj2被load在兩條cache line,如果通路這個對象時cpu需要更多的時鐘,在資料結構時應該避免。可以在定義資料結構時用宏__rte_cache_aligned或加入padding成員。

struct s3
{
    uint32_t x;
    uint16_t y;
}__rte_cache_aligned;

struct s4
{
    uint32_t x;
    uint16_t y;
    uint16_t padding;
}           

複制

資料預取

一般通路CPU的cache效率最高,提前将需要處理的資料load到cache可以提高性能,但預取必須在合适的時間點發起,過早發起預取會導緻資料還沒有被使用就被替換出cache,最終适得其反,是以需要根據實際應用場景和多次嘗試找到最合适的預取時間點。

通常在進行資料包處理時會先對資料包進行預取操作。DPDK提供的接口rte_prefetch0會觸發cpu進行預取操作,如下是預取資料包的示例代碼:

/* Prefetch first packets */
for (j = 0; j < PREFETCH_OFFSET && j < nb_rx; j++) {
	rte_prefetch0(rte_pktmbuf_mtod(pkts_burst[j], void *));
}
/* Prefetch and forward already prefetched packets */
for (j = 0; j < (nb_rx - PREFETCH_OFFSET); j++) {
	rte_prefetch0(rte_pktmbuf_mtod(pkts_burst[j + PREFETCH_OFFSET], void *));
	l3fwd_simple_forward(pkts_burst[j], portid);
}
/* Forward remaining prefetched packets */
for (; j < nb_rx; j++) {
	l3fwd_simple_forward(pkts_burst[j], portid);
}           

複制

其他技巧

  • 對于一些代碼行數不多且經常被調用的函數,定義為靜态内聯函數(static inline function),可以省去函數調用開銷
  • 分支預測,對于經常發生或不常發生的分支使用likely()/unlikely()來幫助編譯器生成更加高效的可執行檔案,減少cache miss

性能瓶頸分析的一般方法

上面提的一些技巧可以幫助在開發過程中規避部分性能陷阱,但僅僅做到這些是不夠的,就像任何程式都有bug一樣,性能瓶頸始終是存在的。通過閱讀代碼很難發現産生瓶頸的原因,這時候就需要借助一些測量工具來幫助定位原因了。

通常作性能瓶頸分析時需要找一個軟體基準版本,給出一個metric值(通常由測試儀器給出,比如時延、吞吐量),然後再通過分析工具定位出産生性能缺陷的代碼,反複修改這部分代碼再給出一個metric值與之間的值作比較,直到metric值達到自己的預期為止。

Linux提供了很多開源工具來分析程式性能,比如iostat、perf、vmstat等。Intel也提供了一款專業的性能分析工具VTune幫助開發人員分析和定位程式性能瓶頸。Intel處理器内部有許多事件計數器,當有事件發生時對應的計數器加一,與程式性能相關的計數器有如下幾種:

  • cache misses
  • 分支預測錯誤
  • DTLB misses
  • 長延時指令和異常

通過檢視這些計數器值大小便可斷定瓶頸原因,這是一種較底層的分析方法,需要對Intel CPU架構有所了解,且不能定位到産生瓶頸的具體代碼行。VTune提供的另外一種分析方法Hotspots,能夠幫助開發人員找出程式中消耗CPU最多的(熱點)函數,通過這些列出的熱點函數可以快速定位到代碼行。通常使用Hotspots分析能夠找出一般常見的性能瓶頸。

VTune提供Windows下的GUI和Linux下的CLI兩種版本。我在項目中一般先用CLI版本的VTune采集運作程式機器的資料,然後将産生的結果移至windows下用GUI版本的VTune來分析,圖形化的界面能夠更利于定位分析。下面是利用VTune分析程式hotspots的demo:

1. 模拟測試環境,運作需要調優的程式;

2. 執行amplxe-cl,指定采集類型和目标程式,開始采集資料,運作結束後會在目前目錄下生成類似r000hs名稱的目錄,裡面存放的是收集的結果

./amplxe-cl -collect hotspots -target-pid=29892           

複制

3. 将目錄拷貝到windows下,用VTune打開檔案r000hs.amplxe

基于DPDK(x86平台)應用性能優化實踐

VTune打開後,出現的是一個關于hotspots的視圖(因為之前指定收集的類型為hotspots,如果指定其他收集類型比如cpu events,則會出現對應視圖)。裡面有多個标簽頁記錄了在采集過程中最耗CPU時間的函數。

Summary标簽頁記錄了程式性能的大概資料,包括CPU耗時,top hotspots和系統資訊。

基于DPDK(x86平台)應用性能優化實踐

Bottom-up标簽頁按函數消耗CPU時間從大到小排序,并可以檢視函數的調用棧,如果目标程式沒有采用編譯優化,VTune甚至能定位到具體代碼行,通過這些資訊就可以很容易找到哪些代碼最消耗CPU時間了。

基于DPDK(x86平台)應用性能優化實踐

在性能調優時,最好使用未經編譯器優化的版本測試,這樣VTune能夠幫助定位到具體的代碼行。采集時間不宜過長,否則會導緻VTune分析緩慢,一般設定為60s即可,可通過amplxe-cl的參數指定,具體使用方法參考http://software.intel.com/zh-cn/blogs/2010/11/10/amplxe-cl。

總結

性能優化是個體力活又是一個細心活,需要反複的代碼修改和測試資料才能找到性能瓶頸所在。熟悉底層開發環境和x86系統結構對性能優化有很大幫助,隻有在了解開發環境之後才能寫出跑得更快的代碼,隻有熟知CPU内部結構才能在優化時提供更多的線索,一款好的分析工具也是必不可少的。本文隻是講了性能優化技巧的冰山一角,更多的是要在實際項目中不斷摸索積累經驗,具體問題具體對待。