DPDK編程指導——編寫高效代碼(翻譯)



34 writing efficient code 編寫有效的代碼
34.1 Memory 內存
本節介紹一些關鍵的內存考慮點,當在DPDK環境開發應用程序時。

34.1.1 Memory Copy: Do not Use libc in the Data Plane 內存拷貝:不要再數據面使用lic

libc中的很多函數不是為性能設計的。例如 memcpy() 或 strcpy() 不應該被用在 data plane。若是拷貝小結構,性能是個簡單的技術,可以被編譯器優化。請參閱《VTune ™性能優化要點》來自Interl新聞的建議。

對於特殊函數,經常被調用的,這是一個好主意,提供一個自制的優化函數,應該被聲明為靜態內聯。

DPDK API 提供了一個優化的 rte_memcpy() 函數。

34.1.2 Memory Allocation 內存申請

libc中的其他函數,像 mallc(),提供了一種靈活的方式申請和釋放內存。在某些情況下,使用動態分配是必須的,但是真的不建議在 data plane 使用 malloc(),因為管理分裂的堆是昂貴的,並且分配器可能未被優化針對並行申請。

如果你真的需要動態分配內存在 data plane,最好使用一個大小固定對象的內存池。librte_mempool提供了這樣的API。這種書結構提供了幾種提升性能的服務,像對象的內存對齊,無鎖訪問對象,NUMA意識,批量 get/put 和 per-lcore 緩存。rte_malloc() 函數使用了桶mempools相似的概念。

數據面動態分配內存使用rte_malloc()。

34.1.3 Concurrent Access to the Same Memory Area 並發訪問同一內存區域

幾個核的讀寫訪問操作對同一內存區域,會產生很多data cache miss,性能消耗非常高。這種情況經常會使用per-lcore變量,例如,在統計的時候。至少有兩個情況適合:
1、使用RTE_PER_LCORE變量
2、使用表結構(每個核一個,即每核的),這種情況,每個結構必須是cache-aligned(緩存對齊)

讀為主的變量可以被多核共享,而沒有性能損耗,如果沒有讀寫變量在同一個cache line中。

34.1.4 NUMA 非統一內存訪問

在NUMA系統上,最好訪問本地內存,因為訪問遠地內存慢。
memzone、ring、rte_malloc 和 mempool 提供了在指定的socket上創建池的接口。

有時候,這是一個好主意,復制一份數據(每個socket一份)來提升速度。對於讀為主經常被訪的變量,讓他們只存在於一個socket上不是問題,因為數據會一直保存着cache中。

34.1.5 Distribution Across Memory Channels 分散訪問內存通道

現代內存控制器有好幾個內存通道可以並行的加載或存儲數據。根據內存控制器和它的配置,通道數和通過通道分配內存的方式各不相同。每個通道有帶寬限制,這意味着,如果一個通道做了所有的內存訪問操作,有一個潛在的瓶頸。

默認情況下,內存庫會在幾個通道之前傳遞對象的地址。

34.2 Communication Between lcores 核間通信

為了提供核間基於消息的通信方式,建議使用DPDK ring API,它提供了一個無鎖的實現。

ring支持批量和突發(bulk/burst)訪問,這意味着它可以從ring里讀取幾個元素,只消耗一次原子操作。當使用批量訪問操作的時候性能有極大的提升。

34.3 PMD Driver

DPDK的Poll Mode Driver同樣能工作在批量和突發(bulk/burst)模式。允許代碼分解為每個調用者在發送或接收功能上。

避免部分寫。當PCI設備通過DMA寫到系統內存時,它消耗很少如果寫操作實在一個完成的cache line上,而不是cache line的一部分上。再PMD的代碼中,已經采取動作盡盡可能的避免部分寫(partial write)。

34.3.1 Lower Packet Latency 低數據包延遲

傳統上,吞吐和延遲間存在一個權衡。可以調整應用程序實現高吞吐,但數據包平均的端到端的延遲將隨之提升。同樣,應用程序可以被調整到至始至終的低延遲,成本是吞吐變低。

為了實現更高的吞吐,DPDK嘗試聚合處理成本,每個數據包融進了突發的數據包處理中。

使用testpmd應用程序作為例子,burst size可以被設置為16通過命令行。這允許應用程序可以一次請求16個數據包從PMD。testpmd應用程序然后立即嘗試傳遞所有收到的數據包,在這里,是16個數據包。

數據包不會被發送,直到tail指針在被網卡相應的發送隊列被更新。這種行為是期望的,當調整為高吞吐,因為RX和TX隊里尾指針更新的成本可以跨越16個包。有效的隱藏寫入PCIe設備相對較慢的MMIO的成本。
然而,這不是非常可取的有對於低延遲,因為被收到的第一個數據包必須等待另外15個包被接收。他不能被傳遞知道其他15個包也被處理了,因為網卡不會知道要傳送數據包,只到TX尾指針被更新。尾指針不會被更新,知道16個包都被處理了。

為了始終保持低延遲,即使在高負載情況下,應用程序開發者也應該避免批量處理。testpmd可以把burst的值調整為1,這樣可以保證一次只處理一個包,提供更低的延遲,但是會降低吞吐。

34.4 Locks and Atomic Operations 鎖和原子操作

原子操作意味着在指令之前暗含着lock前綴,這會引起處理器的LOCK信號被斷言在執行后續指令期間。這在多核環境下對性能有很大的影響。

通過在數據平面避免鎖機制,讓性能得到提升。鎖總是可以被替代通過其他的方法,例如使用每核變量。此外,一些鎖技術更有效比其他的。例如,RCU算法可以頻繁的替換簡單的讀寫鎖。

34.5 Coding Considerations 編碼注意事項

34.5.1 Inline Functions 內聯函數

使用 static inline ,小的函數可以在頭文件中聲明為靜態內聯。避免call質量的消耗,相關聯的上下文的保存。然而這種技術並不是總有效,取決於許多因素,包括編譯器。

34.5.2 Branch Prediction 分支預測

使用 likely()、unlikely()

34.6 Setting the Target CPU Type 設置目標CPU類型

DPDK支持CPU特定微體系結構優化,通過DPDK配置文件里的CONFIG_RTE_MACHINE選項。優化程度取決於編譯器的優化為特定微體系結構,因此最好使用最新的編譯器版本,只要有可能。

如果編譯器版本不支持特定的功能集(例如,Intel AVX指令集),生成過程優雅地降低到任何最新的功能集都被編譯器支持。

因為生成和運行目標可能不相同,產生的二進制文件也包含一個平台檢查,在main()函數之前運行並且檢查當前的機器是否適合運行二進制文件。

隨着編譯器的優化,一套處理器定義被自動添加到生成過程(忽視編譯器版本)。這些定義對應目標CPU應該能夠支持的着指令集。例如,一個編譯的二進制文件對於任何SSE4.2處理器將有RTE_MACHINE_CPUFLAG_SSE4_2定義,因此可以為不同的平台選擇相應的編譯時代碼路徑。

編譯優化 -O3

35 PROFILE YOUR APPLICATION 分析應用程序

英特爾處理器提供的性能計數器來監控事件。由英特爾提供的一些工具可以用來分析和基准測試應用程序。請參閱從英特爾按VTune性能分析器必備出版物以獲取更多信息。
對於DPDK的應用程序,這可以在Linux應用程序只的環境中進行。

應通過事件計數器進行監控的主要情況是:
·Cache misses 高速緩存未命中
·Branch mis-predicts 分支誤預測
·DTLB misses
·Long latency instructions and exceptions 長延遲指令和異常

請參閱英特爾性能分析指南,了解應用程序分析的詳細信息。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM