Java通過零拷貝實(shí)現(xiàn)高效的數(shù)據(jù)傳輸

零拷貝,零開銷

本文僅是中文版本砌庄,原文由 Sathish Palaniappan, Pramod Nagaraja 發(fā)布于 2008年09月2號(hào)。文章適合初次接觸零拷貝技術(shù)并想進(jìn)一步學(xué)習(xí)的讀者,零拷貝本身是一種思想盹舞,不與任何編程語言綁定产镐,不懂Java的讀者可以跳過零拷貝技術(shù)在Java中實(shí)現(xiàn)的具體細(xì)節(jié)。


許多Web應(yīng)用提供大量的靜態(tài)內(nèi)容踢步,主要就是從磁盤讀取數(shù)據(jù)然后將數(shù)據(jù)寫回套接字癣亚,中間不涉及數(shù)據(jù)的變換。這種操作對(duì)CPU的使用相對(duì)較少获印,但是效率很低:首先述雾,內(nèi)核從文件讀取數(shù)據(jù),然后將數(shù)據(jù)從內(nèi)核空間拷貝到用戶進(jìn)程空間兼丰,最后應(yīng)用程序?qū)?shù)據(jù)拷貝回內(nèi)核空間并通過套接字發(fā)送玻孟。實(shí)際上,在整個(gè)流程中應(yīng)用程序僅充當(dāng)一個(gè)將數(shù)據(jù)從磁盤拷貝到套接字的低效中間層地粪。

每次數(shù)據(jù)跨越用戶態(tài)和內(nèi)核態(tài)的邊界取募,數(shù)據(jù)都需要拷貝,拷貝操作消耗CPU和內(nèi)存帶寬蟆技。幸運(yùn)的是通過一種稱為“零拷貝”的技術(shù)可消除這些不必要的拷貝玩敏。使用零拷貝的應(yīng)用要求內(nèi)核將磁盤數(shù)據(jù)直接拷貝到套接字而不再經(jīng)過應(yīng)用。零拷貝可以極大的提高應(yīng)用的性能并減少上下文在內(nèi)核態(tài)和用戶態(tài)之間的切換次數(shù)质礼。

在 Linux 和 Unix 系統(tǒng)中 Java 類庫通過java.nio.channels.FileChanneltransgerTo方法支持零拷貝旺聚。可以使用transgerTo方法在兩個(gè)通道之間直接傳遞數(shù)據(jù)眶蕉,而不要求數(shù)據(jù)經(jīng)過應(yīng)用程序砰粹。為了更好的理解零拷貝技術(shù)對(duì)性能的提升,首先通過傳統(tǒng)復(fù)制語義實(shí)現(xiàn)一個(gè)簡單文件傳輸功能造挽,然后通過零拷貝技術(shù)實(shí)現(xiàn)同樣功能碱璃,并比較兩種實(shí)現(xiàn)在性能上的差異。

數(shù)據(jù)傳輸: 傳統(tǒng)語義

考慮這樣的場景:從文件讀取數(shù)據(jù)并通過網(wǎng)絡(luò)將數(shù)據(jù)傳遞給其他程序(這是很多應(yīng)用的行為饭入,包括提供靜態(tài)內(nèi)容的Web應(yīng)用嵌器,F(xiàn)TP 服務(wù)器,郵件服務(wù)器等)谐丢。兩個(gè)核心的操作如代碼1所示:

代碼 1. 從文件拷貝數(shù)據(jù)到套接字
File.read(fileDesc, buf, len);
Socket.send(socket, buf, len);

雖然代碼1非常的簡單爽航,但是在代碼內(nèi)部實(shí)現(xiàn),拷貝操作需要上下文在用戶態(tài)和內(nèi)核態(tài)切換四次乾忱,在操作完成前數(shù)據(jù)需要拷貝四次讥珍。圖1展示了數(shù)據(jù)如何從文件轉(zhuǎn)移到套接字:

數(shù)據(jù)拷貝路徑
圖 1. 傳統(tǒng)數(shù)據(jù)拷貝方法

圖 2 展示了上下文切換:

上下文切換
圖 2. 傳統(tǒng)方法下的上下文切換

涉及的步驟包括:

  1. read()調(diào)用導(dǎo)致上下文從用戶態(tài)切換到內(nèi)核態(tài)。內(nèi)核通過sys_read()(或等價(jià)的方法)從文件讀取數(shù)據(jù)窄瘟。DMA引擎執(zhí)行第一次拷貝:從文件讀取數(shù)據(jù)并存儲(chǔ)到內(nèi)核空間的緩沖區(qū)衷佃。

  2. 請(qǐng)求的數(shù)據(jù)從內(nèi)核的讀緩沖區(qū)拷貝到用戶緩沖區(qū),然后read()方法返回蹄葱。read()方法返回導(dǎo)致上下文從內(nèi)核態(tài)切換到用戶態(tài)∈弦澹現(xiàn)在待讀取的數(shù)據(jù)已經(jīng)存儲(chǔ)在用戶空間內(nèi)的緩沖區(qū)衰腌。

  3. send()調(diào)用導(dǎo)致上下文從用戶態(tài)切換到內(nèi)核態(tài)。第三次拷貝數(shù)據(jù)從用戶空間重新拷貝到內(nèi)核空間緩沖區(qū)觅赊。但是右蕊,這一次,數(shù)據(jù)被寫入一個(gè)不同的緩沖區(qū)吮螺,一個(gè)與目標(biāo)套接字相關(guān)聯(lián)的緩沖區(qū)饶囚。

  4. send()系統(tǒng)調(diào)用返回導(dǎo)致第四次上下文切換。當(dāng)DMA引擎將數(shù)據(jù)從內(nèi)核緩沖區(qū)傳輸?shù)絽f(xié)議引擎緩沖區(qū)時(shí)鸠补,第四次拷貝是獨(dú)立且異步的萝风。

使用中間內(nèi)核緩沖區(qū)(而不是將數(shù)據(jù)直接發(fā)送到用戶緩沖區(qū))似乎非常低效。但是紫岩,進(jìn)程引入中間內(nèi)核緩沖區(qū)可以提高性能规惰。在讀取端使用中間內(nèi)核緩沖區(qū),在應(yīng)用請(qǐng)求的數(shù)據(jù)沒有超出內(nèi)核緩沖區(qū)的數(shù)據(jù)時(shí)泉蝌,內(nèi)核緩沖區(qū)可以擔(dān)當(dāng)“預(yù)讀緩存”的角色歇万。在寫端,中間內(nèi)核緩沖區(qū)使寫操作完全異步化勋陪。

不幸的是贪磺,當(dāng)請(qǐng)求的數(shù)據(jù)大于內(nèi)核緩沖區(qū)大小時(shí)這種方法往往會(huì)成為性能瓶頸。數(shù)據(jù)在最終被發(fā)送之前诅愚,在磁盤寒锚,內(nèi)核緩沖區(qū)和用戶緩沖區(qū)之間發(fā)生多次拷貝。零拷貝通過減少不必要的數(shù)據(jù)拷貝以提供性能违孝。

數(shù)據(jù)傳輸: 零拷貝方式

如果你回想使用傳統(tǒng)語義傳遞數(shù)據(jù)的場景刹前,你會(huì)發(fā)現(xiàn)第二次和第三次數(shù)據(jù)拷貝并不是真的需要。應(yīng)用程序除了緩存數(shù)據(jù)然后將數(shù)據(jù)傳回套接字緩沖區(qū)外沒有做任何事情雌桑。數(shù)據(jù)可以直接從內(nèi)核的讀緩沖區(qū)傳輸?shù)教捉幼志彌_區(qū)喇喉。transferTo方法允許你實(shí)現(xiàn)這樣的流程。transferTo方法的簽名如代碼 2所示:

代碼 2. The transferTo() method
public void transferTo(long position, long count, WritableByteChannel target);

transferTo 方法將數(shù)據(jù)從文件通道傳輸?shù)浇o定的可寫字節(jié)通道筹燕。transferTo 內(nèi)部實(shí)現(xiàn)依賴底層操作系統(tǒng)對(duì)零拷貝的支持:在UNIX和各 Linux 版本中轧飞,transgerTo方法調(diào)用最終會(huì)調(diào)用sendfile()方法衅鹿,代碼如 List 3 所示撒踪,sendfile將數(shù)據(jù)從一個(gè)文件描述符傳輸?shù)搅硪粋€(gè):

代碼 3. The sendfile() system call
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

代碼 1 中的file.read()socket.send()兩個(gè)方法調(diào)用可以替換為一個(gè)transferTo()方法調(diào)用,如代碼 4所示:

代碼 4. 使用 transferTo() 從磁盤拷貝數(shù)據(jù)到套接字
transferTo(position, count, writableChannel);

圖 3 展示了使用 transferTo() 方法時(shí)大渤,數(shù)據(jù)的流向:

數(shù)據(jù)拷貝路徑
圖 3. 使用transferTo()時(shí)數(shù)據(jù)拷貝

圖 4 展示了使用 transferTo() 方法時(shí)制妄,上下文的切換:

上下文切換
圖 4. 使用 transferTo() 時(shí)上下文切換

使用transgerTo()方法時(shí)涉及的步驟包括以下兩步:

  1. transgerTo方法調(diào)用觸發(fā)DMA引擎將文件上下文信息拷貝到內(nèi)核讀緩沖區(qū),接著內(nèi)核將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到與外出套接字相關(guān)聯(lián)的緩沖區(qū)泵三。

  2. DMA引擎將數(shù)據(jù)從內(nèi)核套接字緩沖區(qū)傳輸?shù)絽f(xié)議引擎(第三次數(shù)據(jù)拷貝)耕捞。

這是一個(gè)改進(jìn):上下文切換的次數(shù)從4次減少到2次衔掸,數(shù)據(jù)拷貝的次數(shù)從4次減少到3次(僅有一次數(shù)據(jù)拷貝消耗CPU資源)。然而俺抽,這并沒有實(shí)現(xiàn)零拷貝的目標(biāo)敞映,如果底層網(wǎng)卡支持gather operations,可以進(jìn)一步減少內(nèi)核拷貝數(shù)據(jù)的次數(shù)磷斧。Linux 內(nèi)核 從2.4 版本開始修改了套接字緩沖區(qū)描述符以滿足這個(gè)要求振愿。這種方法不僅減少了多個(gè)上下文切換,還消除了消耗CPU的重復(fù)數(shù)據(jù)拷貝弛饭。用戶使用的方法沒有任何變化冕末,依然通過transferTo方法,但是方法的內(nèi)部實(shí)現(xiàn)

發(fā)生了變化:

  1. transferTo方法調(diào)用觸發(fā) DMA 引擎將文件上下文信息拷貝到內(nèi)核緩沖區(qū)侣颂。

  2. 數(shù)據(jù)不會(huì)被拷貝到套接字緩沖區(qū)档桃,只有數(shù)據(jù)的描述符(包括數(shù)據(jù)位置和長度)被拷貝到套接字緩沖區(qū)。DMA 引擎直接將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到協(xié)議引擎憔晒,這樣減少了最后一次需要消耗CPU的拷貝操作藻肄。

圖 5 展示了在有gather option條件下使用transferTo時(shí)數(shù)據(jù)拷貝情況:

gather opreation 數(shù)據(jù)拷貝
圖 5. 使用 transferTo() and gather operations 時(shí)的數(shù)據(jù)拷貝

性能測試

現(xiàn)在讓我們在一個(gè)需要在客戶端和服務(wù)器之間傳輸文件的程序中應(yīng)用零拷貝。 TraditionalClient.javaTraditionalServer.java 基于傳統(tǒng)復(fù)制語義拒担,使用 File.read()Socket.send()方法讀取和發(fā)送數(shù)據(jù). TraditionalServer.java 是一個(gè)監(jiān)聽在5230端口等待客戶端連接的服務(wù)器應(yīng)用仅炊,每次從套接字中讀取4kb的數(shù)據(jù)。 TraditionalClient.java 連接到服務(wù)器, 使用File.read() 方法每次從文件讀取4kb數(shù)據(jù)澎蛛,然后調(diào)用方法socket.send()) 將數(shù)據(jù)通過套接字發(fā)送給服務(wù)器.

類似的, TransferToServer.javaTransferToClient.java 通過使用transferTo()(使用sendfile()系統(tǒng)調(diào)用發(fā)送數(shù)據(jù))實(shí)現(xiàn)一樣的將數(shù)據(jù)從客戶端發(fā)送到服務(wù)器的功能抚垄。

性能比較

在Linux 內(nèi)核2.6版本上,以毫秒統(tǒng)計(jì)使用傳統(tǒng)方法和使用transferTo方法傳輸不同大小的文件的耗時(shí)谋逻。表1展示了測試結(jié)果:

表 1. 性能標(biāo)膠: 傳統(tǒng)方法 vs. 零拷貝
File size Normal file transfer (ms) transferTo (ms)
7MB 156 45
21MB 337 128
63MB 843 387
98MB 1320 617
200MB 2124 1150
350MB 3631 1762
700MB 13498 4422
1GB 18399 8537

從測試結(jié)果來看使用transgerTo的API和傳統(tǒng)方法相比可以降低65%的傳輸時(shí)間呆馁。這可以有效的提高在不同I/O通道之間大量拷貝數(shù)據(jù)應(yīng)用的性能。

總結(jié)

我們已經(jīng)證明了在從一個(gè)通道讀取數(shù)據(jù)并將相同的數(shù)據(jù)寫入另一個(gè)通道的場景下使用transferTo帶來的巨大性能優(yōu)勢毁兆。內(nèi)部緩沖區(qū)的拷貝浙滤,盡管這些拷貝隱藏在內(nèi)核里,但是也是可觀的消耗气堕。對(duì)于需要處理在通道之間拷貝大量數(shù)據(jù)的應(yīng)用纺腊,零拷貝技術(shù)可以顯著的提升性能。性能測試使用的用例可以從Github免費(fèi)下載茎芭。

擴(kuò)展閱讀

在Java編程領(lǐng)域揖膜,Netty是一個(gè)非常流行的基于事件驅(qū)動(dòng)的異步網(wǎng)絡(luò)應(yīng)用框架,Netty的核心框架之一就是擁有豐富的支持零拷貝的字節(jié)緩沖區(qū)梅桩,想進(jìn)一步了解零拷貝技術(shù)的朋友可以深入研究Netty中零拷貝技術(shù)的實(shí)現(xiàn)壹粟。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宿百,隨后出現(xiàn)的幾起案子趁仙,更是在濱河造成了極大的恐慌洪添,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雀费,死亡現(xiàn)場離奇詭異干奢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)盏袄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門律胀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人貌矿,你說我怎么就攤上這事炭菌。” “怎么了逛漫?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵黑低,是天一觀的道長。 經(jīng)常有香客問我酌毡,道長克握,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任枷踏,我火速辦了婚禮菩暗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旭蠕。我一直安慰自己停团,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布掏熬。 她就那樣靜靜地躺著佑稠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪旗芬。 梳的紋絲不亂的頭發(fā)上舌胶,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音疮丛,去河邊找鬼幔嫂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛誊薄,可吹牛的內(nèi)容都是我干的履恩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼暇屋,長吁一口氣:“原來是場噩夢啊……” “哼似袁!你這毒婦竟也來了洞辣?” 一聲冷哼從身側(cè)響起咐刨,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤昙衅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后定鸟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體而涉,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年联予,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了啼县。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沸久,死狀恐怖季眷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卷胯,我是刑警寧澤子刮,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站窑睁,受9級(jí)特大地震影響挺峡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜担钮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一橱赠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧箫津,春花似錦狭姨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至暖眼,卻和暖如春惕耕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诫肠。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國打工司澎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人栋豫。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓挤安,卻偏偏與公主長得像,于是被迫代替她去往敵國和親丧鸯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛤铜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容