零拷贝
DMA拷贝
image.pngDMA:direct memory access 直接内存拷贝,不使用CPU。
1.将硬盘上的数据,通过DMA拷贝到内核(内核buffer)
2.cpu拷贝到用户buffer,应用程序在用户buffer中操作数据
3.通过cpu拷贝到socket buffer,再通过DMA拷贝到协议栈
可以看出传统的拷贝一共经过了四次拷贝,磁盘(DMA拷贝)->内核(CPU拷贝)->用户buffer(CPU 拷贝)->socket buffer(DMA拷贝)->协议栈
三次上下文切换。
MMAP优化
- mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内存空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。
- 需要进行4次上下文切换,3次数据拷贝。
- 适合小数据量的读写。
拷贝过程
1.Hard drive (硬件)通过DMA内存映射到 内核缓冲区
2.因为mmap映射,内核与用户空间可以共享数据,所以这一步没有进行拷贝
3.内核空间通过CPU 拷贝到socket buffer
4.socket buffer 通过DMA 拷贝到协议栈
一共进行了三次拷贝,三次上下文切换。
sendFile优化的IO读写
- Linux2.1 版本提供了
sendFile
函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到SocketBuffer
,同时,由于和用户态完全无关,就减少了一次上下文切换。 - 需要3次上下文切换和最少2次数据拷贝,但是仍然有CPU 拷贝。
- 适合大文件的传输。
零拷贝
而 Linux 在 2.4 版本中,做了一些修改,避免了从内核缓冲区拷贝到 Socket Buffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。
image.pngnotice:
n1.零拷贝
零拷贝是从操作系统来说的,指的是没有CPU 拷贝。零拷贝不仅仅带来了更少的数据复制,还减少了上下文切换,更减少了CPU缓存(cpu缓存伪共享和cpu校验和计算)
n2.其实并不是完全规避了CPU copy
这里其实有一次CPU拷贝,kernel buffer -> socket buffer
。但是,拷贝的信息很少,只拷贝了数据的长度、偏移量等关键信息,消耗低,可以忽略不计。
如果忽略内核空间到socket buffer的化,此时拷贝操作只进行了两次:hard drive (DMA copy) ->kernel buffer (DMA copy)->protocol engine
传统拷贝和零拷贝代码
传统IO
File file = new File("test.txt");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
byte[] arr = new byte[(int) file.length()];
raf.read(arr);
Socket socket = new ServerSocket(8080).accept();
socket.getOutputStream().write(arr);
NIO中的零拷贝(transferTo)
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 7001));
//得到一个文件CHANNEl
FileChannel channel = new FileInputStream("a.zip").getChannel();
//准备发送
long startTime = System.currentTimeMillis();
//在Linux下一个 transferTo 方法就可以完成传输
//在windows 下一次调用 transferTo 只能发送 8M,就需要分段传输文件
//传输时的位置
//transferTo 底层使用到零拷贝
long transferCount = channel.transferTo(0, channel.size(), socketChannel);
System.out.println("发送的总的字节数:" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime));
channel.close();
}