【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 . 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 . 總結 : 上述進行了 444 次拷貝 , 333 次用戶態與內核態之間的狀態切換 , 代價很高 ;
① 拷貝次數分析 : 開始時數據存儲在 硬盤文件 中 , 直接內存拷貝 ( Direct Memory Access ) 到 內核緩沖區 , CPU 拷貝 到 用戶緩沖區 , CPU 拷貝 到 Socket 緩沖區 , 直接內存拷貝 ( Direct Memory Access ) 到 協議棧 ;
硬盤文件 -> 內核緩沖區 ( 內核空間 ) -> 用戶緩沖區 ( 用戶空間 ) -> Socket 緩沖區 ( 內核空間 ) -> 協議棧
② 狀態改變分析 : 開始運行的是用戶應用程序 , 起始狀態肯定是用戶態 , 之后將硬盤文件數據拷貝到內核緩沖區后 , 轉為內核態 , 之后又拷貝到了用戶緩沖區 , 轉為用戶態 ; 數據寫出到 Socket 緩沖區 , 又轉為內核態 , 最后再切換成用戶態 , 執行后續應用程序代碼邏輯 ;
用戶態 -> 內核態 -> 用戶態 -> 內核態 -> 用戶態
[1][1][1] DMA 全稱 ( Direct Memory Access ) , 直接內存拷貝 , 該拷貝通過內存完成 , 不涉及 CPU 參與 ;
三、 mmap 內存映射 ( 3拷貝 4切換 )
將硬盤中的文件映射到 內核緩沖區 , 用戶空間中的應用程序也可以訪問該 內核緩沖區 中的數據 , 使用這種機制 , 原來的 444 次數據拷貝減少到了 333 次 ,
1 . mmap 數據拷貝過程 :
① 硬盤文件 -> 內核緩沖區 : 硬盤文件數據 , DMA 拷貝到 內核緩沖區 中 , 應用程序可以直接訪問該 內核緩沖區中的數據 ;
② 內核緩沖區 -> Socket 緩沖區 : 內核緩沖區 數據 , 通過 CPU 拷貝到 Socket 緩沖區 ;
③ Socket 緩沖區 -> 協議棧 : Socket 緩沖區 數據 , 通過 DMA 拷貝到 協議棧 ;
硬盤文件 -> 內核緩沖區 ( 內核空間 ) -> Socket 緩沖區 ( 內核空間 ) -> 協議棧
2 . mmap 狀態切換 : 其狀態切換還是 333 次 , 由初始狀態 用戶態 , 在拷貝數據到內核緩沖區時 , 切換成內核態 , 訪問該內核緩沖區數據時 , 又切換成用戶態 , 將數據拷貝到 Socket 緩沖區時 , 切換成內核態 , 最后再切換成用戶態 , 執行后續應用程序代碼邏輯 ;
用戶態 -> 內核態 -> 用戶態 -> 內核態 -> 用戶態
四、 sendFile 函數 ( Linux 2.1 優化 ) ( 3拷貝2切換 )
sendFile 是 Linux 提供的函數 , 其實現了由 內核緩沖區 直接將數據拷貝到 Socket 緩沖區 , 該操作直接在內核空間完成 , 不經過用戶空間 , 沒有用戶態參與 , 因此 減少了一次用戶態切換 ;
此次優化 , 由原來的 444 次拷貝 , 333 次狀態切換 , 變成 333 次拷貝 , 222 次狀態切換 ;
1 . sendFile 函數 數據拷貝分析 :
① 硬盤文件 -> 內核緩沖區 : 硬盤文件數據 , DMA 拷貝到 內核緩沖區 中 ;
② 內核緩沖區 -> Socket 緩沖區 : 內核緩沖區 數據 , 通過 CPU 拷貝到 Socket 緩沖區 ;
③ Socket 緩沖區 -> 協議棧 : Socket 緩沖區 數據 , 通過 DMA 拷貝到 協議棧 ;
硬盤文件 -> 內核緩沖區 ( 內核空間 ) -> Socket 緩沖區 ( 內核空間 ) -> 協議棧
2 . sendFile 函數 狀態切換分析 : 其狀態切換只有 222 次 , 由初始狀態 用戶態 , 在拷貝數據到內核緩沖區時 , 切換成內核態 , 在內核態直接將數據拷貝到 Socket 緩沖區時 , 還是處于內核狀態 , 之后拷貝到協議棧時 , 變成用戶狀態 ;
用戶態 -> 內核態 -> 用戶態
五、 sendFile 函數 ( Linux 2.4 優化 ) ( 2拷貝 2切換 )
sendFile 是 Linux 提供的函數 , 其在 Linux 2.4 版本中 , 直接將數據從 內核緩沖區 拷貝到 協議棧 中 ;
此次優化 , 由原來的 444 次拷貝 , 333 次狀態切換 , 變成 222 次拷貝 , 222 次狀態切換 ;
1 . sendFile 函數 數據拷貝分析 : 全稱 DMA 拷貝 , 沒有 CPU 拷貝 ;
① 硬盤文件 -> 內核緩沖區 : 硬盤文件數據 , DMA 拷貝到 內核緩沖區 中 ;
② 內核緩沖區 -> -> 協議棧 : 通過 DMA 拷貝 , 將 內核緩沖區 中的數據直接拷貝到 協議棧 ;
硬盤文件 -> 內核緩沖區 ( 內核空間 ) -> 協議棧
2 . sendFile 函數 狀態切換分析 : 其狀態切換只有 222 次 , 由初始狀態 用戶態 , 在拷貝數據到內核緩沖區時 , 切換成內核態 , 在內核態直接將數據拷貝到協議棧時 , 變成用戶狀態 ;
用戶態 -> 內核態 -> 用戶態
3 . 少量 CPU 拷貝 : 該機制還存在少量的 CPU 拷貝 , 其 對性能的消耗忽略不計 ; 這些 CPU 拷貝操作是從 內核緩沖區 中將數據的長度 ( Length ) , 偏移量 ( Offset ) 拷貝到 Socket 緩沖區 ;
總結
以上是生活随笔為你收集整理的【Netty】mmap 和 sendFile 零拷贝原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Netty】NIO 网络编程 聊天室案
- 下一篇: 【Netty】零拷贝案例 ( trans