應用場景:將本地一個文件經過網絡傳輸給另外一個程序 關鍵字:數據複製過程當中,內容不進行修改java
zero-copy技術的使用場景有不少,好比Kafka, 又或者是Netty等,能夠大大提高程序的性能 下面咱們經過對比傳統方式和zero-copy的方式傳輸數據,來理解zero-copy。linux
代碼以下:bash
// 將文件讀取到buf中
File.read(fileDesc, buf, len);
// 將buf中的數據寫入到socket中
Socket.send(socket, buf, len);
複製代碼
結合下圖理解理解操做以下:網絡
和 詳細步驟以下: 1. 調用File.read()方法會發生上下文切換(context switch),從user mode切換到kernel mode。在read()內部會調用sys_read()來從文件中讀取數據。第一次copy由DMA (direct memory access)完成,將文件內容從磁盤讀出,並存儲在kernel空間的buffer中,爲方便說明,這個buffer稱爲reader buffer。 2. 而後請求的數據被從kernel空間的buffer copy到user buffer中,這是第二次copy。調用的返回又觸發了第二次上下文切換,從kernel mode返回到user mode。至此,數據存儲在user buffer中。 3. Socket.send()會觸發了第三次上下文切換,從user mode到kernel mode,並執行第三次copy,將數據從user buffer從新複製到kernel的buffer中。固然,此次的kernel buffer和第一步的kernel buffer不是同一個buffer,此次buffer和目標socket關聯,命名爲socket buffer。 4. 完成copy後,Socket.send()返回時,同時也形成了第四次上下文切換。同時第四次copy發生,DMA egine將數據從kernel buffer複製到網卡設備(protocol engine)中。第四次copy是獨立並且異步的。結論: 以上操做要經歷4次user mode和kernel mode之間的上下文切換,數據都被拷貝了4次。經過上面的分析,咱們發現第2步和第3步數據copy是多餘,系統徹底能夠將文件讀取到kernel buffer中後,直接將kernel buffer中的數據寫入socket。爲了實現這個優化,linux引入了zero copy。異步
要支持zero-copy要知足以下條件: a. 操做系統的要求:linux 2.4及以上版本的內核中,而且網卡支持 gather operation。socket
java中使用 FileChannel的transferTo()和transferFrom()方法使用zero-copy,若是底層系統支持zero-copy性能
代碼:測試
public void transferTo(long position, long count, WritableByteChannel target);
// transferTo()方法底層會調用系統方法sendfile()
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
複製代碼
結合下圖理解理解操做以下: a. 執行transferTo()方法後,會發生上下文切換,從user mode到kernel mode,而後將文件內容copy到kernel buffer(稱爲Read buffer),這一操做由DMA engine完成。。 b. 和傳統方式不一樣,zero-copy沒有將數據copy到socket buffer,只有數據的描述信息(如數據的位置和長度)存儲到socket buffer中。而後DMA engine直接把數據從kernel buffer傳輸到網卡設備(protocol engine)。 完成複製後,又會發生上下文切換,從kernel mode到user mode優化
結論: 1. Zero-copy上下文切換的次數從4次下降到2次,數據複製次數從4次下降到2次 2. Zero-copy 中數據的copy都由DMA執行,CPU不參與複製,從而節省CPU的消耗 3. Zero-copy中的zero不是指不須要copy,而是指user mode到kernel mode copy數據的次數爲零ui
支持zero-copy的操做系統 除了Linux系統,其餘系統也支持zero-copy • Microsoft Windows經過TransmitFile API支持zero-copy • macOS supports zero-copy through the FreeBSD portion of the kernel
下面咱們使用代碼來比較傳統方式和zero-copy的傳輸效率 測試代碼:客服端從本地讀取文件,經過網絡傳輸到服務端
測試代碼來源:developer.ibm.com/articles/j-… 不過在測試過程當中,此源碼在測試有問題,咱們對測試代碼進行修改: TransferToClient.java
// 原來的版本,這裏會有問題,由於transferTo()一次最多發送8388608個字節,須要循環發送。
long position = 0;
while(curnset != 0) {
curnset = fc.transferTo(position, fsize, sc);
position += curnset;
System.out.println("total bytes transferred--" + position + " and time taken in MS--" + (System.currentTimeMillis() - start));
}
複製代碼
TraditionalServer.java
// nread判斷條件修改成 nread <=0,而不是nread==0
byte[] byteArray = new byte[4096];
while(true) {
int nread = input.read(byteArray , 0, 4096);
if (nread <= 0)
break;
}
}
複製代碼
文件傳輸花費時間對比
結論:zero-copy的傳輸效率比傳統的方式快一倍