提升 Linux 網路效能,應付 100 GB的網路卡

oschina發表於2015-01-30

賈斯玻.布魯勒在2015年澳大利亞Linux研討會(LCA)的有關核心的小型研討會上提到:100GB的網路卡即將來臨(見幻燈片,PDF格式的)。對Linux核心來說,要以最大的速度驅動這樣的介面卡將是巨大的挑戰。應對這一挑戰是目前和未來一段時間內工作的重心。好訊息是Linux網路通訊速度已經有了很大的提高-不過還有一些問題有待解決。

挑戰

由於網路介面卡的速度越來越快,那麼傳送資料包的時間間隔(也就是核心處理一個包的時間)就會越來越短。就當前正在使用的10GB介面卡而言,傳送兩個1538位元組資料包的時間間隔是1230納秒,40GB的網路通訊會把這個時間間隔大幅縮減到307納秒。很自然,100GB的網路介面卡會急劇縮減這個時間間隔,即縮減一個包的處理時間大約為120納秒。此時,網路卡每秒就要處理八百一十五萬個資料包。這樣就不會有太多時間來進行資料包的處理了。

那麼,如果像大多數人一樣沒有100GB的網路介面卡把玩一下,你該怎麼做呢? 你可以替代性地使用10GB的網路介面卡傳送很小的幀。乙太網所能傳送的最小幀長度是84位元組;賈斯玻說使用10G網路介面卡傳送最小尺寸的乙太網幀的時間間隔是67.2納秒。能夠處理最小尺寸乙太網幀負載的系統就適宜於處理100GB的網路通訊,不過目前還未實現。而且對這種負載的處理也非常困難:對一個3GHz的CPU來說,處理一個包只需要200個CPU週期。賈斯玻還提醒說:這點時間本身就不算多。

以往,核心就沒有在處理密集型網路通訊的工作負載方面做太多工作。這也使得大量次要的網路通訊實現完全不包含在核心網路協議棧中。如此的系統需求就表明核心沒有充分利用上硬體;由單個CPU執行的次要實現就可以以最大速度驅動介面卡,這就是核心主線開發犯難的地方。

賈斯玻說現在的問題是:核心開發人員過去曾經集中力量新增多核支援。在此情形下,他們就不會關心單核執行效率的衰退情況。由此而產生的結果就是:現今的網路通訊協議棧可以很好地處理許多種工作負載,然而對於哪些對時延特別敏感的工作負載則無能為力了。現今的核心每秒每核心只能處理一百萬到兩百萬之間個資料包,而某些不使用核心的方法每秒每核心可處理的資料包數可達一千五百萬個。

預算所需時間

如果你打算解決這個問題,那麼你就需要仔細地看看處理一個包的每一個步驟所花費的時間。例如,賈斯玻提到的3GHz的處理器上出現一次高速緩衝未命中需要32納秒才找到對應的資料。因此,只要有兩次未命中就可以佔完處理一個資料包所需要的時間。假如套接字緩衝(“SKB”)在64位系統上佔有四個高速緩衝行,而且許多套接字緩衝(“SKB”)都是在包處理期間寫入的,那麼問題的第一部分就非常明顯了-四次高速緩衝未命中需要的時間將會比可使用的時間更長。

除上述外,X86架構上的鎖的原子級別的操作大概需要費時8.25ns. 也就是說,在實踐中一次最短的自旋鎖的上鎖/解鎖的過程就要耗時16ns還多一點。 所以儘管有大量的鎖操作,但是在這方面卻沒有多少空間可以降低延時。

接下來是與系統進行一次通訊的時間消耗。在安裝有SELinux及審計功能(auditing enabled)的系統上,一次系統的呼叫就要耗時75ns — 超過了幀處理的整個時間。 禁用審計和SELinux可以減少時間,使系統通訊降到42ns以下。這看上去好多了,但是還是很大的時間消耗。有許多方法可以在處理多個包的時候降低時間消耗,包括呼叫sendmmsg(),recvmmsg(),sendfile()和splice()這樣的函式。但是在實際中,有開發者覺得這些函式的表現並不如預期的那麼好,但是他們又找不出原因。Chirstoph Lameter站在旁觀者的角度指出來說,那些對延時敏感的使用者可以傾向於使用InfiniBand架構的“IB verbs”機制。

賈斯玻詢問:如果上面所要花費的時間已經確定,那麼不需要網路通訊的方案是如何獲得更高的效能呢?關鍵在於批量進行操作處理和資源的預分配和預獲取。這樣的操作始終在CPU範圍內執行,而且不需要上鎖。這些因素對於縮減包的後設資料和減少系統呼叫的次數來說也非常重要。要想在速度上再提高一點,哪些可充分利用高速緩衝的資料結構可以幫一些忙。在上面所述的所有技術中,批量進行操作處理是最最重要的。一個包所需要的處理時間可能無法接受,但如果是一次性執行多個包的話,你就很容易接受了。一個包加鎖需要16納秒讓人很受傷,如果一次性加鎖16個包,那麼每個包所需要的處理時間就縮減到1納秒了。

對批量操作的改進

因此,毫不奇怪,賈斯玻的工作過去曾經集中精力改進網路層的批量操作。其中包括 TCP 層的批量傳輸工作,這裡十月份的文章中有所介紹;有關它是如何執行的詳細情況請參閱上面連結的那篇文章。簡要的來說,機制是這樣的:通知網路驅動還有更多的資料包等著傳輸,讓驅動延遲花費時間較多的操作,直到所有的包都填滿佇列。如果把這樣的修改放在適當的地方,那麼在同樣小的資料包一個接著一個傳輸的情況下,他編寫的系統每秒至少能傳輸一千四百八十萬個資料包。

他說做到這些技巧在於給網路通訊層新增了進行批量操作處理的應用程式介面(API),同時不會增加系統延遲時間。通常,延遲和吞吐量之間必須要進行某種權衡;然而,在這裡,延遲和吞吐量都有所改進。特別難於處理的技巧是對傳輸延遲的推測-這就好比賭博後續的包馬上就到來。這樣的技巧常常用在改進基準測試的結果上,在現實世界中的工作負載上極少用到。

批量操作可以並且應當在通訊協議棧的的多個層實現。例如,排隊子系統(“qdisc”)就是非常適合進行批量操作;畢竟,排隊就意味著延遲。目前,在最理想的情況下,排隊子系統(“qdisc”) 編碼部分處理一個包需要六個鎖操作-僅鎖操作就需要48納秒。而對一個包進行排隊處理的全部時間是58-68納秒,可見大量的時間都用在鎖操作上了。賈斯玻已經新增了批量操作,這樣就可以把所消耗的時間分散在對多個包的處理上了,不過,這段程式碼實只在對包進行排隊的情況下才執行。

排隊子系統(“qdisc”)程式碼名義上的最快執行路徑只有在不進行排隊的情況下才執行;此時,這些包通常直接傳送給網路介面,根本就不需要進行排隊處理。不過目前,處理這些包依然需要完完整整地進行6次鎖操作。他說,改進這樣的處理過程是可能的。無鎖的排隊子系統幾乎不需要花費時間對包進行排隊處理,賈斯玻對現有的實現進行了測試,確定了哪些地方可以優化,他說,至少剔除48納秒的鎖操作就值得一試。

他說,傳輸的效能現在看起來已經夠好了,不過接收的處理過程仍然可以進行改進。一個調整的非常好的接收系統每秒可接收的最大包數目大約是六百五十萬個-不過這種情況只在接收到包後即刻就丟棄的情況下才發生。優化接收過程的一些工作一直在進行著,最大處理速度可提升到每秒處理九百多萬個資料包。然而,對優化後的基準測試也暴露出了問題,它沒有給出與記憶體管理子系統互動所花費的時間。

記憶體管理

有證據表明與記憶體管理系統的互動很花時間。網路通訊棧的接收過程似乎採用了這樣的行為:使用slab分配器時其效能不是最佳。接收程式碼每次可給高達64個包進行記憶體空間分配,而傳輸過程的批處理操作中可釋放記憶體的包數目達256個。這樣的模式似乎特意把SLUB分配器用到了相對處理較慢的程式碼段上了。賈斯玻對其進行了一些小型基準測試,他發現在kmam_cache_free()後只有呼叫kemem_cache_alloc()一次需要大約19納秒。然而,當完成256次分配和釋放時,所需要的時間就會增加到40納秒。實際的網路通訊中,記憶體分配和釋放是與其他操作同時進行的,這樣記憶體分配和釋放所花費的時間就會更多,高達77納秒-超過預計分配給它的時間。

因此,賈斯玻得出這樣的結論:要麼必須對記憶體管理程式碼部分進行改進,要麼必須通過某鍾方式完全繞過這段程式碼。為了看看後一種方法是否可行,他實現了qmempool子系統;這個子系統可以以無鎖的方式批量進行記憶體分配和釋放。通過使用qmempool,他在簡單測試中節省了12納秒,而在包轉發測試中則節省高達40納秒。qmempool為了使自身執行更快採用了許多技術,然而殺手級技術是批量處理操作。

賈斯玻平靜地說:qmempool的實現是一種激勵。他想去表明哪些地方可以進行修改,同時鼓勵記憶體管理系統的開發人員在這方面做些工作。記憶體管理團隊的回應將在下一次談話中介紹,我們將單獨對其進行報導。

相關文章