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

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

传统数据copy方式

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

零拷贝简析及Java中的相关实现_第1张图片
传统数据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

零拷贝简析及Java中的相关实现_第2张图片
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

零拷贝简析及Java中的相关实现_第3张图片
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 (可以看作完全实现了零拷贝)

零拷贝简析及Java中的相关实现_第4张图片
Linux 2.4+ 版本

原理

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

Java Nio 零拷贝

使用transferTo()/transferFrom()函数

原理

/*
 * 

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.

* */ 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();
    }
}

你可能感兴趣的:(零拷贝简析及Java中的相关实现)