大白话解释,零拷贝就是没有把数据从一个存储区域拷贝到另一个存储区域。但是没有数据的复制,怎么可能实现数据的传输呢?其实我们在java NIO、netty、kafka遇到的零拷贝,并不是不复制数据,而是减少不必要的数据拷贝次数,从而提升代码性能
直接内存访问(Direct Memory Access)(DMA)
缓冲区 是所有I/O的基础,I/O 无非就是把数据移进或移出缓冲区
虚拟内存:现代操作系统都使用虚拟内存,有如下两个好处
利用第一点特性可以把内核空间地址和用户空间的虚拟地址映射到同一个物理地址,这样DMA就可以填充(读写)对内核和用户空间进程同时可见的缓冲区了;大致如下
#include <unistd>
ssize_t write(int filedes, void *buf, size_t nbytes);
ssize_t read(int filedes, void *buf, size_t nbytes);
一共有四次用户空间与内核空间的上下文切换。四次数据copy,分别是两次CPU数据复制,两次DMA数据复制
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
通过mmap实现的零拷贝I/O进行了4次用户空间与内核空间的上下文切换,以及3次数据拷贝;其中3次数据拷贝中包括了2次DMA拷贝和1次CPU拷贝
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
通过sendfile实现的零拷贝I/O使用了2次用户空间与内核空间的上下文切换,以及3次数据的拷贝。其中3次数据拷贝中包括了2次DMA拷贝和1次CPU拷贝
带有DMA收集拷贝功能的sendfile实现的I/O使用了2次用户空间与内核空间的上下文切换,以及2次数据的拷贝,而且这2次的数据拷贝都是非CPU拷贝。这样一来我们就实现了最理想的零拷贝I/O传输了,不需要任何一次的CPU拷贝,以及最少的上下文切换
java NIO的零拷贝实现是基于mmap+write方式
FileChannel的map方法产生的MappedByteBuffer
FileChannel提供了map()方法,该方法可以在一个打开的文件和MappedByteBuffer之间建立一个虚拟内存映射,MappedByteBuffer继承于ByteBuffer;该缓冲器的内存是一个文件的内存映射区域。map方法底层是通过mmap实现的,因此将文件内存从磁盘读取到内核缓冲区后,用户空间和内核空间共享该缓冲区。用法如下
public void main(String[] args){
try {
FileChannel readChannel = FileChannel.open(Paths.get("./cscw.txt"), StandardOpenOption.READ);
FileChannel writeChannel = FileChannel.open(Paths.get("./siting.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, 1024 * 1024 * 40);
//数据传输
writeChannel.write(data);
readChannel.close();
writeChannel.close();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
FileChannel的transferTo、transferFrom
如果操作系统底层支持的话,transferTo、transferFrom也会使用相关的零拷贝技术来实现数据的传输。用法如下
public void main(String[] args) {
try {
FileChannel readChannel = FileChannel.open(Paths.get("./cscw.txt"), StandardOpenOption.READ);
FileChannel writeChannel = FileChannel.open(Paths.get("./siting.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
long len = readChannel.size();
long position = readChannel.position();
//数据传输
readChannel.transferTo(position, len, writeChannel);
//效果和transferTo 一样的
//writeChannel.transferFrom(readChannel, position, len, );
readChannel.close();
writeChannel.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
手机扫一扫
移动阅读更方便
你可能感兴趣的文章