零拷貝, DMA(Direct Memory Access) copy, 即直接內(nèi)存拷貝(不使用CPU拷貝), 是網(wǎng)絡(luò)編程的關(guān)鍵偿枕,很多IO相關(guān)的性能優(yōu)化都離不開出革。
傳統(tǒng)數(shù)據(jù)copy方式
經(jīng)過了4次拷貝和3次狀態(tài)切換, 其中有2次DMA Copy
步驟
- 從硬盤中讀取數(shù)據(jù), 使用DMA Copy拷貝到內(nèi)核buffer中
- 再使用CPU Copy將數(shù)據(jù)從內(nèi)核buffer中拷貝到用戶buffer
- 再使用CPU Copy將數(shù)據(jù)從用戶buffer拷貝到socket buffer
- 再使用DMA Copy將數(shù)據(jù)從socket buffer拷貝到協(xié)議棧中
mmap優(yōu)化
經(jīng)過了3次拷貝和3次狀態(tài)切換, 其中有2次DMA copy
原理
memory map(內(nèi)存映射), 通過內(nèi)存映射选泻,將文件映射到內(nèi)核緩沖區(qū)耀怜,同時(shí)亮隙,用戶空間可以共享內(nèi)核空間的數(shù)據(jù)某弦。這樣桐汤,在進(jìn)行網(wǎng)絡(luò)傳輸時(shí),就可以減少內(nèi)核空間到用戶控件的拷貝次數(shù)靶壮。
步驟
- 從硬盤中讀取數(shù)據(jù), 使用DMA Copy拷貝到內(nèi)核buffer中
- 使用mmap將文件映射到內(nèi)核buffer中和用戶buffer共享, 無需再次拷貝到用戶buffer中
- 再使用CPU Copy將數(shù)據(jù)從用戶buffer拷貝到socket buffer
- 再使用DMA Copy將數(shù)據(jù)從socket buffer拷貝到協(xié)議棧中
sendFile優(yōu)化
Linux 2.4 -
經(jīng)過了3次拷貝和2次狀態(tài)切換, 其中有2次DMA copy
原理
Linux在2.4版本之前提供的 sendFile 函數(shù)怔毛,其基本原理如下:數(shù)據(jù)根本不經(jīng)過用戶態(tài),直接從內(nèi)核緩沖區(qū)進(jìn)入到 Socket Buffer亮钦,同時(shí)馆截,由于和用戶態(tài)完全無關(guān),就減少了一次上下文切換蜂莉。
步驟
- 從硬盤中讀取數(shù)據(jù), 使用DMA Copy拷貝到內(nèi)核buffer中
- 再使用CPU Copy將數(shù)據(jù)從內(nèi)核buffer直接拷貝到socket buffer
- 再使用DMA Copy將數(shù)據(jù)從socket buffer拷貝到協(xié)議棧中
Linux 2.4 +
經(jīng)過了2次拷貝, 其中近似沒有CPU copy (可以看作完全實(shí)現(xiàn)了零拷貝)
原理
相對(duì)于之前Linux版本中的sendFile做了一些修改蜡娶,極大地避免了從內(nèi)核緩沖區(qū)CPU Copy到 Socket buffer 的操作(這里其實(shí)會(huì)有一次cpu拷貝,但是拷貝的信息很少, 主要是一些描述信息, 比如lenght , offset, 這些信息會(huì)從kernel buffer拷貝到socket buffer, 但是過程中的消耗非常低,可以忽略)映穗,直接拷貝到協(xié)議棧窖张,從而再一次減少了數(shù)據(jù)拷貝。
Java Nio 零拷貝
使用
transferTo()
/transferFrom()
函數(shù)
原理
/*
* <p> This method is potentially much more efficient than a simple loop
* that reads from the source channel and writes to this channel. Many
* operating systems can transfer bytes directly from the source channel
* into the filesystem cache without actually copying them. </p>
*
*/
public abstract long transferFrom(ReadableByteChannel src, long position, long count) throws IOException;
許多操作系統(tǒng)可以直接將字節(jié)從源通道轉(zhuǎn)移到文件系統(tǒng)緩存中蚁滋,而無需實(shí)際復(fù)制它們, 而transferTo()
/transferFrom()
函數(shù)底層使用MappedByteBuffer
來實(shí)現(xiàn), 可以讓文件直接在內(nèi)存(堆外內(nèi)存)中進(jìn)行修改, 操作系統(tǒng)不需要再拷貝一次, MappedByteBuffer
操作內(nèi)存相關(guān)代碼請(qǐng)點(diǎn)擊查看宿接。
代碼
public static void transferFrom() {
File sourceFile = new File("./source.log");
File targetFile = new File("./target1.log");
try (
FileInputStream fileInputStream = new FileInputStream(sourceFile);
FileChannel fileInputStreamChannel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream(targetFile);
FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
) {
fileOutputStreamChannel.transferFrom(fileInputStreamChannel, 0, fileInputStreamChannel.size());
} catch (Exception e) {
e.printStackTrace();
}
}