【Netty】mmap 和 sendFile 零拷贝原理
文章目錄
- 一、 零拷貝 簡介
- 二、 傳統(tǒng) BIO 數(shù)據(jù)拷貝分析 ( 4拷貝 4切換 )
- 三、 mmap 內(nèi)存映射 ( 3拷貝 4切換 )
- 四、 sendFile 函數(shù) ( Linux 2.1 優(yōu)化 ) ( 3拷貝2切換 )
- 五、 sendFile 函數(shù) ( Linux 2.4 優(yōu)化 ) ( 2拷貝 2切換 )
一、 零拷貝 簡介
零拷貝作用 : 在網(wǎng)絡(luò)編程中 , 如果要進(jìn)行性能優(yōu)化 , 肯定要涉及到零拷貝 , 使用零拷貝能極大的提升數(shù)據(jù)傳輸性能 ;
零拷貝類型 : mmap ( 內(nèi)存映射 ) 和 sendFile;
數(shù)據(jù)角度分析 : 在零拷貝機(jī)制中 , 整個(gè)數(shù)據(jù)在內(nèi)存中只有一份數(shù)據(jù) , 非零拷貝機(jī)制中 , 內(nèi)核緩沖區(qū) , 用戶緩沖區(qū) , Socket 緩沖區(qū) , 各有一份數(shù)據(jù) ;
零拷貝指的是沒有 CPU 拷貝 , 都是 DMA ( 直接內(nèi)存訪問 ) 拷貝 ;
零拷貝性能優(yōu)勢 : 沒有復(fù)制數(shù)據(jù)帶來的內(nèi)存開銷 , 沒有 CPU 拷貝 , 直接節(jié)省了大量 CPU 計(jì)算資源 ;
二、 傳統(tǒng) BIO 數(shù)據(jù)拷貝分析 ( 4拷貝 4切換 )
傳統(tǒng) BIO 數(shù)據(jù)拷貝代碼示例 :
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 {// 客戶端與服務(wù)器端連接過程忽略, 主要是分析數(shù)據(jù)拷貝過程Socket socket = new Socket();InetSocketAddress inetSocketAddress =new InetSocketAddress(Inet4Address.getLocalHost(), 8888);socket.connect(inetSocketAddress);// 分析下面過程中, 數(shù)據(jù)拷貝次數(shù), 和用戶態(tài)與內(nèi)核態(tài)的轉(zhuǎn)換次數(shù)// 1. 從文件中讀取數(shù)據(jù)FileInputStream fileInputStream = new FileInputStream("file.txt");byte[] buffer = new byte[1024];// 首先將硬盤中的文件, 進(jìn)行 DMA 拷貝, 此處對應(yīng) read 方法, // 將文件數(shù)據(jù)從硬盤中拷貝到 內(nèi)核緩沖區(qū) ( 用戶態(tài)切換成內(nèi)核態(tài) )// 將內(nèi)核緩沖區(qū)中的數(shù)據(jù), 通過 CPU 拷貝 方式, 拷貝到 用戶緩沖區(qū) ( 內(nèi)核態(tài)切換成用戶態(tài) )int readLen = fileInputStream.read(buffer);// 2. 寫出數(shù)據(jù)到服務(wù)器// 將用戶緩沖區(qū)中的數(shù)據(jù), 再次通過 CPU 拷貝方式, 拷貝到 Socket 緩沖區(qū) ( 用戶態(tài)切換成內(nèi)核態(tài) )// 再次使用 DMA 拷貝, 將 Socket 緩沖區(qū)中的數(shù)據(jù)拷貝到 協(xié)議棧 ( Protocol Engine ) 中socket.getOutputStream().write(buffer, 0, readLen);} catch (IOException e) {e.printStackTrace();}} }分析上述代碼中數(shù)據(jù)拷貝次數(shù) , 用戶態(tài)與內(nèi)核態(tài)狀態(tài)切換 ;
1 . fileInputStream.read(buffer) 操作數(shù)據(jù)拷貝及狀態(tài)轉(zhuǎn)換分析 :
① 硬盤 ( 初始用戶態(tài) ) -> 內(nèi)核緩沖區(qū) ( 內(nèi)核態(tài) ) : 首先將硬盤中的文件 , 進(jìn)行 DMA[1]^{[1]}[1] 拷貝 , 此處對應(yīng) read 方法 , 將文件數(shù)據(jù)從硬盤中拷貝到 內(nèi)核緩沖區(qū) ; ( 用戶態(tài)切換成內(nèi)核態(tài) )
② 內(nèi)核緩沖區(qū) ( 內(nèi)核態(tài) ) -> 用戶緩沖區(qū) ( 用戶態(tài) ) : 將內(nèi)核緩沖區(qū)中的數(shù)據(jù) , 通過 CPU 拷貝 方式 , 拷貝到 用戶緩沖區(qū) ; ( 內(nèi)核態(tài)切換成用戶態(tài) )
2 . socket.getOutputStream().write(buffer, 0, readLen) 操作數(shù)據(jù)拷貝及狀態(tài)轉(zhuǎn)換分析 :
① 用戶緩沖區(qū) ( 用戶態(tài) ) -> Socket 緩沖區(qū) ( 內(nèi)核態(tài) ) : 將用戶緩沖區(qū)中的數(shù)據(jù) , 再次通過 CPU 拷貝 方式 , 拷貝到 Socket 緩沖區(qū) ; ( 用戶態(tài)切換成內(nèi)核態(tài) )
② Socket 緩沖區(qū) ( 內(nèi)核態(tài) ) -> 協(xié)議棧 : 再次使用 DMA[1]^{[1]}[1] 拷貝 , 將 Socket 緩沖區(qū)中的數(shù)據(jù)拷貝到 協(xié)議棧 ( Protocol Engine ) 中 ;
3 . 總結(jié) : 上述進(jìn)行了 444 次拷貝 , 333 次用戶態(tài)與內(nèi)核態(tài)之間的狀態(tài)切換 , 代價(jià)很高 ;
① 拷貝次數(shù)分析 : 開始時(shí)數(shù)據(jù)存儲(chǔ)在 硬盤文件 中 , 直接內(nèi)存拷貝 ( Direct Memory Access ) 到 內(nèi)核緩沖區(qū) , CPU 拷貝 到 用戶緩沖區(qū) , CPU 拷貝 到 Socket 緩沖區(qū) , 直接內(nèi)存拷貝 ( Direct Memory Access ) 到 協(xié)議棧 ;
硬盤文件 -> 內(nèi)核緩沖區(qū) ( 內(nèi)核空間 ) -> 用戶緩沖區(qū) ( 用戶空間 ) -> Socket 緩沖區(qū) ( 內(nèi)核空間 ) -> 協(xié)議棧
② 狀態(tài)改變分析 : 開始運(yùn)行的是用戶應(yīng)用程序 , 起始狀態(tài)肯定是用戶態(tài) , 之后將硬盤文件數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)后 , 轉(zhuǎn)為內(nèi)核態(tài) , 之后又拷貝到了用戶緩沖區(qū) , 轉(zhuǎn)為用戶態(tài) ; 數(shù)據(jù)寫出到 Socket 緩沖區(qū) , 又轉(zhuǎn)為內(nèi)核態(tài) , 最后再切換成用戶態(tài) , 執(zhí)行后續(xù)應(yīng)用程序代碼邏輯 ;
用戶態(tài) -> 內(nèi)核態(tài) -> 用戶態(tài) -> 內(nèi)核態(tài) -> 用戶態(tài)
[1][1][1] DMA 全稱 ( Direct Memory Access ) , 直接內(nèi)存拷貝 , 該拷貝通過內(nèi)存完成 , 不涉及 CPU 參與 ;
三、 mmap 內(nèi)存映射 ( 3拷貝 4切換 )
將硬盤中的文件映射到 內(nèi)核緩沖區(qū) , 用戶空間中的應(yīng)用程序也可以訪問該 內(nèi)核緩沖區(qū) 中的數(shù)據(jù) , 使用這種機(jī)制 , 原來的 444 次數(shù)據(jù)拷貝減少到了 333 次 ,
1 . mmap 數(shù)據(jù)拷貝過程 :
① 硬盤文件 -> 內(nèi)核緩沖區(qū) : 硬盤文件數(shù)據(jù) , DMA 拷貝到 內(nèi)核緩沖區(qū) 中 , 應(yīng)用程序可以直接訪問該 內(nèi)核緩沖區(qū)中的數(shù)據(jù) ;
② 內(nèi)核緩沖區(qū) -> Socket 緩沖區(qū) : 內(nèi)核緩沖區(qū) 數(shù)據(jù) , 通過 CPU 拷貝到 Socket 緩沖區(qū) ;
③ Socket 緩沖區(qū) -> 協(xié)議棧 : Socket 緩沖區(qū) 數(shù)據(jù) , 通過 DMA 拷貝到 協(xié)議棧 ;
硬盤文件 -> 內(nèi)核緩沖區(qū) ( 內(nèi)核空間 ) -> Socket 緩沖區(qū) ( 內(nèi)核空間 ) -> 協(xié)議棧
2 . mmap 狀態(tài)切換 : 其狀態(tài)切換還是 333 次 , 由初始狀態(tài) 用戶態(tài) , 在拷貝數(shù)據(jù)到內(nèi)核緩沖區(qū)時(shí) , 切換成內(nèi)核態(tài) , 訪問該內(nèi)核緩沖區(qū)數(shù)據(jù)時(shí) , 又切換成用戶態(tài) , 將數(shù)據(jù)拷貝到 Socket 緩沖區(qū)時(shí) , 切換成內(nèi)核態(tài) , 最后再切換成用戶態(tài) , 執(zhí)行后續(xù)應(yīng)用程序代碼邏輯 ;
用戶態(tài) -> 內(nèi)核態(tài) -> 用戶態(tài) -> 內(nèi)核態(tài) -> 用戶態(tài)
四、 sendFile 函數(shù) ( Linux 2.1 優(yōu)化 ) ( 3拷貝2切換 )
sendFile 是 Linux 提供的函數(shù) , 其實(shí)現(xiàn)了由 內(nèi)核緩沖區(qū) 直接將數(shù)據(jù)拷貝到 Socket 緩沖區(qū) , 該操作直接在內(nèi)核空間完成 , 不經(jīng)過用戶空間 , 沒有用戶態(tài)參與 , 因此 減少了一次用戶態(tài)切換 ;
此次優(yōu)化 , 由原來的 444 次拷貝 , 333 次狀態(tài)切換 , 變成 333 次拷貝 , 222 次狀態(tài)切換 ;
1 . sendFile 函數(shù) 數(shù)據(jù)拷貝分析 :
① 硬盤文件 -> 內(nèi)核緩沖區(qū) : 硬盤文件數(shù)據(jù) , DMA 拷貝到 內(nèi)核緩沖區(qū) 中 ;
② 內(nèi)核緩沖區(qū) -> Socket 緩沖區(qū) : 內(nèi)核緩沖區(qū) 數(shù)據(jù) , 通過 CPU 拷貝到 Socket 緩沖區(qū) ;
③ Socket 緩沖區(qū) -> 協(xié)議棧 : Socket 緩沖區(qū) 數(shù)據(jù) , 通過 DMA 拷貝到 協(xié)議棧 ;
硬盤文件 -> 內(nèi)核緩沖區(qū) ( 內(nèi)核空間 ) -> Socket 緩沖區(qū) ( 內(nèi)核空間 ) -> 協(xié)議棧
2 . sendFile 函數(shù) 狀態(tài)切換分析 : 其狀態(tài)切換只有 222 次 , 由初始狀態(tài) 用戶態(tài) , 在拷貝數(shù)據(jù)到內(nèi)核緩沖區(qū)時(shí) , 切換成內(nèi)核態(tài) , 在內(nèi)核態(tài)直接將數(shù)據(jù)拷貝到 Socket 緩沖區(qū)時(shí) , 還是處于內(nèi)核狀態(tài) , 之后拷貝到協(xié)議棧時(shí) , 變成用戶狀態(tài) ;
用戶態(tài) -> 內(nèi)核態(tài) -> 用戶態(tài)
五、 sendFile 函數(shù) ( Linux 2.4 優(yōu)化 ) ( 2拷貝 2切換 )
sendFile 是 Linux 提供的函數(shù) , 其在 Linux 2.4 版本中 , 直接將數(shù)據(jù)從 內(nèi)核緩沖區(qū) 拷貝到 協(xié)議棧 中 ;
此次優(yōu)化 , 由原來的 444 次拷貝 , 333 次狀態(tài)切換 , 變成 222 次拷貝 , 222 次狀態(tài)切換 ;
1 . sendFile 函數(shù) 數(shù)據(jù)拷貝分析 : 全稱 DMA 拷貝 , 沒有 CPU 拷貝 ;
① 硬盤文件 -> 內(nèi)核緩沖區(qū) : 硬盤文件數(shù)據(jù) , DMA 拷貝到 內(nèi)核緩沖區(qū) 中 ;
② 內(nèi)核緩沖區(qū) -> -> 協(xié)議棧 : 通過 DMA 拷貝 , 將 內(nèi)核緩沖區(qū) 中的數(shù)據(jù)直接拷貝到 協(xié)議棧 ;
硬盤文件 -> 內(nèi)核緩沖區(qū) ( 內(nèi)核空間 ) -> 協(xié)議棧
2 . sendFile 函數(shù) 狀態(tài)切換分析 : 其狀態(tài)切換只有 222 次 , 由初始狀態(tài) 用戶態(tài) , 在拷貝數(shù)據(jù)到內(nèi)核緩沖區(qū)時(shí) , 切換成內(nèi)核態(tài) , 在內(nèi)核態(tài)直接將數(shù)據(jù)拷貝到協(xié)議棧時(shí) , 變成用戶狀態(tài) ;
用戶態(tài) -> 內(nèi)核態(tài) -> 用戶態(tài)
3 . 少量 CPU 拷貝 : 該機(jī)制還存在少量的 CPU 拷貝 , 其 對性能的消耗忽略不計(jì) ; 這些 CPU 拷貝操作是從 內(nèi)核緩沖區(qū) 中將數(shù)據(jù)的長度 ( Length ) , 偏移量 ( Offset ) 拷貝到 Socket 緩沖區(qū) ;
總結(jié)
以上是生活随笔為你收集整理的【Netty】mmap 和 sendFile 零拷贝原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Netty】NIO 网络编程 聊天室案
- 下一篇: 【Netty】零拷贝案例 ( trans