【Netty】mmap 和 sendFile 零拷貝原理
文章目錄

一、 零拷貝 簡介
二、 傳統 BIO 數據拷貝分析 ( 4拷貝 4切換 )
三、 mmap 內存映射 ( 3拷貝 4切換 )
四、 sendFile 函數 ( Linux 2.1 優化 ) ( 3拷貝2切換 )
五、 sendFile 函數 ( Linux 2.4 優化 ) ( 2拷貝 2切換 )
一、 零拷貝 簡介
零拷貝作用 : 在網絡編程中 , 如果要進行性能優化 , 肯定要涉及到零拷貝 ,
使用零拷貝能極大的提升數據傳輸性能 ;
零拷貝類型 : mmap ( 內存映射 ) 和 sendFile;
數據角度分析 : 在零拷貝機制中 , 整個數據在內存中只有一份數據 , 非零拷貝機制中 , 內核緩沖區 , 用戶緩沖區 , Socket 緩沖區 , 各有一份數據 ;
零拷貝指的是沒有 CPU 拷貝 , 都是 DMA ( 直接內存訪問 ) 拷貝 ;
零拷貝性能優勢 : 沒有復制數據帶來的內存開銷 , 沒有 CPU 拷貝 , 直接節省了大量 CPU 計算資源 ;
二、 傳統 BIO 數據拷貝分析 ( 4拷貝 4切換 )
傳統 BIO 數據拷貝代碼示例 :
package kim.hsl.nio.zerocopy; import java.io.FileInputStream; import java.io.IOException; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.Socket; public class BIOClientDemo { public static void main(String[] args) { try { // 客戶端與服務器端連接過程忽略, 主要是分析數據拷貝過程 Socket socket = new Socket(); InetSocketAddress inetSocketAddress = new InetSocketAddress(Inet4Address.getLocalHost(), 8888); socket.connect(inetSocketAddress); // 分析下面過程中, 數據拷貝次數, 和用戶態與內核態的轉換次數 // 1. 從文件中讀取數據 FileInputStream fileInputStream = new FileInputStream("file.txt"); byte[] buffer = new byte[1024]; // 首先將硬盤中的文件, 進行 DMA 拷貝, 此處對應 read 方法, // 將文件數據從硬盤中拷貝到 內核緩沖區 ( 用戶態切換成內核態 ) // 將內核緩沖區中的數據, 通過 CPU 拷貝 方式, 拷貝到 用戶緩沖區 ( 內核態切換成用戶態 ) int readLen = fileInputStream.read(buffer); // 2. 寫出數據到服務器 // 將用戶緩沖區中的數據, 再次通過 CPU 拷貝方式, 拷貝到 Socket 緩沖區 ( 用戶態切換成內核態 ) // 再次使用 DMA 拷貝, 將 Socket 緩沖區中的數據拷貝到 協議棧 ( Protocol Engine ) 中 socket.getOutputStream().write(buffer, 0, readLen); } catch (IOException e) { e.printStackTrace(); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
分析上述代碼中數據拷貝次數 , 用戶態與內核態狀態切換 ;
1 .
fileInputStream.read(buffer)
操作數據拷貝及狀態轉換分析 :
① 硬盤 ( 初始用戶態 ) -> 內核緩沖區 ( 內核態 ) : 首先將硬盤中的文件 , 進行
DMA [ 1 ] ^{[1]} [1] 拷貝
, 此處對應 read 方法 , 將文件數據從硬盤中拷貝到 內核緩沖區 ; ( 用戶態切換成內核態 )
② 內核緩沖區 ( 內核態 ) -> 用戶緩沖區 ( 用戶態 ) : 將內核緩沖區中的數據 , 通過
CPU 拷貝
方式 , 拷貝到 用戶緩沖區 ; ( 內核態切換成用戶態 )
2 .
socket.getOutputStream().write(buffer, 0, readLen)
操作數據拷貝及狀態轉換分析 :
① 用戶緩沖區 ( 用戶態 ) -> Socket 緩沖區 ( 內核態 ) : 將用戶緩沖區中的數據 , 再次通過
CPU 拷貝
方式 , 拷貝到 Socket 緩沖區 ; ( 用戶態切換成內核態 )
② Socket 緩沖區 ( 內核態 ) -> 協議棧 : 再次使用
DMA [ 1 ] ^{[1]} [1] 拷貝
, 將 Socket 緩沖區中的數據拷貝到 協議棧 ( Protocol Engine ) 中 ;
3 . 總結 : 上述進行了 4 4 4 次拷貝 , 3 3 3 次用戶態與內核態之間的狀態切換 , 代價很高 ;
① 拷貝次數分析 : 開始時數據存儲在 硬盤文件 中 ,
直接內存拷貝 ( Direct Memory Access )
到 內核緩沖區 ,
CPU 拷貝
到 用戶緩沖區 ,
CPU 拷貝
到 Socket 緩沖區 ,
直接內存拷貝 ( Direct Memory Access )
到 協議棧 ;
硬盤文件 -> 內核緩沖區 ( 內核空間 ) -> 用戶緩沖區 ( 用戶空間 ) -> Socket 緩沖區 ( 內核空間 ) -> 協議棧
② 狀態改變分析 : 開始運行的是用戶應用程序 ,
起始狀態肯定是用戶態
, 之后將硬盤文件數據拷貝到內核緩沖區后 ,
轉為內核態
, 之后又拷貝到了用戶緩沖區 ,
轉為用戶態
; 數據寫出到 Socket 緩沖區 ,
又轉為內核態 ,
最后再切換成用戶態 , 執行后續應用程序代碼邏輯 ;
用戶態 -> 內核態 -> 用戶態 -> 內核態 -> 用戶態
[ 1 ] [1] [1] DMA 全稱 ( Direct Memory Access ) ,
直接內存拷貝
,
該拷貝通過內存完成
, 不涉及 CPU 參與 ;
三、 mmap 內存映射 ( 3拷貝 4切換 )
將硬盤中的文件映射到 內核緩沖區 , 用戶空間中的應用程序也可以訪問該 內核緩沖區 中的數據 , 使用這種機制 , 原來的 4 4 4 次數據拷貝減少到了 3 3 3 次 ,
1 . mmap 數據拷貝過程 :
① 硬盤文件 -> 內核緩沖區 : 硬盤文件數據 , DMA 拷貝到 內核緩沖區 中 ,
應用程序可以直接訪問該 內核緩沖區中的數據 ;
② 內核緩沖區 -> Socket 緩沖區 : 內核緩沖區 數據 , 通過 CPU 拷貝到 Socket 緩沖區 ;
③ Socket 緩沖區 -> 協議棧 : Socket 緩沖區 數據 , 通過 DMA 拷貝到 協議棧 ;
硬盤文件 -> 內核緩沖區 ( 內核空間 ) -> Socket 緩沖區 ( 內核空間 ) -> 協議棧
2 . mmap 狀態切換 : 其狀態切換還是 3 3 3 次 ,
由初始狀態 用戶態 , 在拷貝數據到內核緩沖區時 , 切換成內核態 ,
訪問該內核緩沖區數據時 , 又切換成用戶態 ,
將數據拷貝到 Socket 緩沖區時 , 切換成內核態 ,
最后再切換成用戶態 , 執行后續應用程序代碼邏輯 ;
用戶態 -> 內核態 -> 用戶態 -> 內核態 -> 用戶態
四、 sendFile 函數 ( Linux 2.1 優化 ) ( 3拷貝2切換 )
sendFile 是 Linux 提供的函數 , 其實現了由
內核緩沖區
直接將數據拷貝到
Socket 緩沖區
, 該操作直接在內核空間完成 , 不經過用戶空間 , 沒有用戶態參與 , 因此
減少了一次用戶態切換 ;
此次優化 , 由原來的 4 4 4 次拷貝 , 3 3 3 次狀態切換 , 變成 3 3 3 次拷貝 , 2 2 2 次狀態切換 ;
1 . sendFile 函數 數據拷貝分析 :
① 硬盤文件 -> 內核緩沖區 : 硬盤文件數據 , DMA 拷貝到 內核緩沖區 中 ;
② 內核緩沖區 -> Socket 緩沖區 : 內核緩沖區 數據 , 通過 CPU 拷貝到 Socket 緩沖區 ;
③ Socket 緩沖區 -> 協議棧 : Socket 緩沖區 數據 , 通過 DMA 拷貝到 協議棧 ;
硬盤文件 -> 內核緩沖區 ( 內核空間 ) -> Socket 緩沖區 ( 內核空間 ) -> 協議棧
2 . sendFile 函數 狀態切換分析 : 其狀態切換只有 2 2 2 次 ,
由初始狀態 用戶態 , 在拷貝數據到內核緩沖區時 , 切換成內核態 ,
在內核態直接將數據拷貝到 Socket 緩沖區時 , 還是處于內核狀態 ,
之后拷貝到協議棧時 , 變成用戶狀態 ;
用戶態 -> 內核態 -> 用戶態
五、 sendFile 函數 ( Linux 2.4 優化 ) ( 2拷貝 2切換 )
sendFile 是 Linux 提供的函數 , 其在 Linux 2.4 版本中 , 直接將數據從
內核緩沖區
拷貝到
協議棧
中 ;
此次優化 , 由原來的 4 4 4 次拷貝 , 3 3 3 次狀態切換 , 變成 2 2 2 次拷貝 , 2 2 2 次狀態切換 ;
1 . sendFile 函數 數據拷貝分析 : 全稱 DMA 拷貝 , 沒有 CPU 拷貝 ;
① 硬盤文件 -> 內核緩沖區 : 硬盤文件數據 , DMA 拷貝到 內核緩沖區 中 ;
② 內核緩沖區 -> -> 協議棧 : 通過 DMA 拷貝 , 將 內核緩沖區 中的數據直接拷貝到 協議棧 ;
硬盤文件 -> 內核緩沖區 ( 內核空間 ) -> 協議棧
2 . sendFile 函數 狀態切換分析 : 其狀態切換只有 2 2 2 次 ,
由初始狀態 用戶態 , 在拷貝數據到內核緩沖區時 , 切換成內核態 ,
在內核態直接將數據拷貝到協議棧時 , 變成用戶狀態 ;
用戶態 -> 內核態 -> 用戶態
3 . 少量 CPU 拷貝 : 該機制還存在少量的 CPU 拷貝 , 其 對性能的消耗忽略不計 ; 這些 CPU 拷貝操作是從
內核緩沖區
中將數據的長度 ( Length ) , 偏移量 ( Offset ) 拷貝到
Socket 緩沖區
;
Linux Socket編程
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。