零拷贝简析及Java中的相关实现

零拷贝, DMA(Direct Memory Access) copy, 即直接内存拷贝(不使用CPU拷贝), 是网络编程的关键,很多IO相关的性能优化都离不开。

传统数据copy方式

经过了4次拷贝和3次状态切换, 其中有2次DMA Copy

传统数据copy

步骤

  1. 从硬盘中读取数据, 使用DMA Copy拷贝到内核buffer中
  2. 再使用CPU Copy将数据从内核buffer中拷贝到用户buffer
  3. 再使用CPU Copy将数据从用户buffer拷贝到socket buffer
  4. 再使用DMA Copy将数据从socket buffer拷贝到协议栈中

mmap优化

经过了3次拷贝和3次状态切换, 其中有2次DMA copy

mmap优化

原理

memory map(内存映射), 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户控件的拷贝次数。

步骤

  1. 从硬盘中读取数据, 使用DMA Copy拷贝到内核buffer中
  2. 使用mmap将文件映射到内核buffer中和用户buffer共享, 无需再次拷贝到用户buffer中
  3. 再使用CPU Copy将数据从用户buffer拷贝到socket buffer
  4. 再使用DMA Copy将数据从socket buffer拷贝到协议栈中

sendFile优化

Linux 2.4 -

经过了3次拷贝和2次状态切换, 其中有2次DMA copy

Linux 2.4- 版本 sendFile
原理

Linux在2.4版本之前提供的 sendFile 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换。

步骤
  1. 从硬盘中读取数据, 使用DMA Copy拷贝到内核buffer中
  2. 再使用CPU Copy将数据从内核buffer直接拷贝到socket buffer
  3. 再使用DMA Copy将数据从socket buffer拷贝到协议栈中

Linux 2.4 +

经过了2次拷贝, 其中近似没有CPU copy (可以看作完全实现了零拷贝)

Linux 2.4+ 版本

原理

相对于之前Linux版本中的sendFile做了一些修改,极大地避免了从内核缓冲区CPU Copy到 Socket buffer 的操作(这里其实会有一次cpu拷贝,但是拷贝的信息很少, 主要是一些描述信息, 比如lenght , offset, 这些信息会从kernel buffer拷贝到socket buffer, 但是过程中的消耗非常低,可以忽略),直接拷贝到协议栈,从而再一次减少了数据拷贝。

Java Nio 零拷贝

使用transferTo()/transferFrom()函数

原理

/*
 * <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;

许多操作系统可以直接将字节从源通道转移到文件系统缓存中,而无需实际复制它们, 而transferTo()/transferFrom()函数底层使用MappedByteBuffer来实现, 可以让文件直接在内存(堆外内存)中进行修改, 操作系统不需要再拷贝一次, MappedByteBuffer操作内存相关代码请点击查看

代码

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();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 先提出两个问题:IO过程中,哪些步骤进行了拷贝?哪些地方零拷贝?Java支持哪些零拷贝? 带着这俩问题,我们一起来...
    消失er阅读 19,558评论 9 43
  • 参见:Java中的零拷贝 先提出两个问题: IO过程中,哪些步骤进行了拷贝?哪些地方零拷贝? Java支持哪些零拷...
    小苏c阅读 2,241评论 0 1
  • 什么是零拷贝 维基上是这么描述零拷贝的:零拷贝描述的是CPU不执行拷贝数据从一个存储区域到另一个存储区域的任务,这...
    tomas家的小拨浪鼓阅读 26,722评论 11 61
  • KAFKA推送消息用到了sendfile,落盘技术用到了mmap,DMA贯穿其中。 先说说零拷贝 零拷贝并不是不需...
    胖虎大哥阅读 14,954评论 2 27
  • 于十月。 想表达却不知道说什么的时候,说说自己的事情是最容易的。随笔相对随意和练笔,便像日记一样写下自己的事。 日...
    写写走走阅读 1,170评论 0 0