IO中的零拷贝
内存的用户空间和内核空间
内存中有用户空间提供给用户调用
内存中内核空间给操作系统的指令使用
DMA 直接存储访问
DMA:直接存储访问,即不会经过CPU的运算,直接由DMA总线进行拷贝
当我们执行read(new byte[10])
时,发送的调用过程
上下文切换
是指CPU执行用户进程执行时处于用户态,当用户进程调用系统函数时,CPU中断用户执行,切换为内核态,执行系统指令。
普通的文件传输方式
普通的方式需要4次上下文切换、2次DMA拷贝、2次CPU运算拷贝
内存映射(MMAP)方式
MMAP的方式就在CPU拷贝上做了一次优化,把内核的内存空间进行映射,映射到内存的用户空间上,这样对用户空间的操作实际是在对内核空间做操作。
Linux的sendFile()优化
Linux2.1的方式
sendFile()
为Linux2.1后增加的系统函数,sendfile系统调用的引入,不仅减少了数据复制,还减少了上下文切换的次数
Linux2.4的方式
这还不是最后效果呢,Linux为了消除内核产生的数据冗余,就是CPU拷贝会产生一些数据副本,提出了一个可以在多块内存空间发拷贝给网卡,所以需要网卡支持聚合操作特性。这个特性意味着待发送的数据可以不用存在地址连续的内存空间中,可以分散存放。
具体做法是在内核版本2.4中,socket缓冲区描述符结构发生了改动,以适应聚合操作的要求——这就是Linux中所谓的"零拷贝“。这种方式不仅减少了多个上下文切换,而且消除了数据冗余。
所以需要提升是2个前提的
- 支持聚合操作的硬件可以从多个内存位置收集数据,从而消除另一个副本。
- Linux对socket的缓冲区描述符进行改动,以支持聚合操作
NIO中的零拷贝
内存
堆内存
在NIO中
ByteBuffer heapBuffer = ByteBuffer.allocate(n);
在Netty中
ByteBuf heapBuf = Unpooled.buffer(n);
特点:
- 优点:在用户态使用声明和访问的时候速度更快
- 缺点:在数据传输到外部时,要进行用户空间到内核空间的一次CPU运算拷贝,
堆外内存
通过mmap
的方式声明堆外内存
在NIO中
ByteBuffer directBuffer = ByteBuffer.allocateDirect(n);
在Netty中
ByteBuf directBuf = Unpooled.buffer(n);
特点:
- 优点:数据传输到外部时,不需要进行用户空间到内核空间的拷贝
- 缺点:声明和访问会慢一点
文件传输
sendFile
方式
FileChannel channel = new RandomAccessFile(file_name, "rw").getChannel();
FileChannel copyChannel = new RandomAccessFile(copy_name, "rw").getChannel();
copyChannel.transferFrom(channel, 0, channel.size());
channel.close();
copyChannel.close();
mmap
方式
FileChannel channel = new RandomAccessFile(file_name, "rw").getChannel();
FileChannel copyChannel = new RandomAccessFile(copy_name, "rw").getChannel();
MappedByteBuffer mapped = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size());
copyChannel.write(mapped);
copyChannel.transferFrom(channel, 0, channel.size());
channel.close();
copyChannel.close();
Netty中的零拷贝
内存:
和NIO一样的,Netty也有2种内存:
ByteBuf directBuf = Unpooled.directBuffer(1024);
ByteBuf heapBuf = Unpooled.buffer(1024);
通过debug窗口可以查看heapBuf和directBuf最明显的区别就是有一个数组,数组是存储在堆中的。
文件传输
在Netty中既能通过mmap
的方式,也能通过transfer(sendFile)
的方式传输文件。
sendFile
方式
Netty中提供了FileRegion
实现零拷贝传输文件的,底层就是调用了sendFile
,在应用层就是以transfer
的字眼出现。
RandomAccessFile file = new RandomAccessFile(fileName, "rw");
FileRegion fileRegion = new DefaultFileRegion(file.getChannel(),0,file.length());
ctx.writeAndFlush(fileRegion);
mmap
方式
RandomAccessFile file = new RandomAccessFile(fileName, "rw");
MappedByteBuffer map = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
ByteBuf byteBuf = Unpooled.wrappedBuffer(map);
ctx.writeAndFlush(byteBuf);