什么是零拷贝
WIKI中对其有如下定义:
“Zero-copy” describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.
从WIKI的定义中,我们看到“零拷贝”是指计算机操作的过程中,CPU不需要为数据在内存之间的拷贝消耗资源。而它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。
零拷贝给我们带来的好处
- 减少甚至完全避免不必要的CPU拷贝,从而让CPU解脱出来去执行其他的任务
- 减少内存带宽的占用
- 通常零拷贝技术还能够减少用户空间和操作系统内核空间之间的上下文切换
零拷贝的实现
零拷贝实际的实现并没有真正的标准,取决于操作系统如何实现这一点。零拷贝完全依赖于操作系统。操作系统支持,就有;不支持,就没有。不依赖Java本身。
传统I/O
在Java中,我们可以通过InputStream从源数据中读取数据流到一个缓冲区里,然后再将它们输入到OutputStream里。我们知道,这种IO方式传输效率是比较低的。那么,当使用上面的代码时操作系统会发生什么情况:
传统IO.jpg
这是一个从磁盘文件读取并且通过socket写出的过程,对应的系统调用如下:
read(file,tmp_buf,len)
write(socket,tmp_buf,len)
- 程序使用read()系统调用。系统由用户态转换为内核态(第一次上线文切换),磁盘中的数据有DMA(Direct Memory Access)的方式读取到内核缓冲区(kernel buffer)。DMA过程中CPU不需要参与数据的读写,而是DMA处理器直接将硬盘数据通过总线传输到内存中。
- 系统由内核态转换为用户态(第二次上下文切换),当程序要读取的数据已经完成写入内核缓冲区以后,程序会将数据由内核缓存区,写入用户缓存区),这个过程需要CPU参与数据的读写。
- 程序使用write()系统调用。系统由用户态切换到内核态(第三次上下文切换),数据从用户态缓冲区写入到网络缓冲区(Socket Buffer),这个过程需要CPU参与数据的读写。
- 系统由内核态切换到用户态(第四次上下文切换),网络缓冲区的数据通过DMA的方式传输到网卡的驱动(存储缓冲区)中(protocol engine)
可以看到,传统的I/O方式会经过4次用户态和内核态的切换(上下文切换),两次CPU中内存中进行数据读写的过程。这种拷贝过程相对来说比较消耗资源
内存映射方式I/O
tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);
这是使用的系统调用方法,这种方式的I/O原理就是将用户缓冲区(user buffer)的内存地址和内核缓冲区(kernel buffer)的内存地址做一个映射,也就是说系统在用户态可以直接读取并操作内核空间的数据。
- mmap()系统调用首先会使用DMA的方式将磁盘数据读取到内核缓冲区,然后通过内存映射的方式,使用户缓冲区和内核读缓冲区的内存地址为同一内存地址,也就是说不需要CPU再讲数据从内核读缓冲区复制到用户缓冲区。
- 当使用write()系统调用的时候,cpu将内核缓冲区(等同于用户缓冲区)的数据直接写入到网络发送缓冲区(socket buffer),然后通过DMA的方式将数据传入到网卡驱动程序中准备发送。
可以看到这种内存映射的方式减少了CPU的读写次数,但是用户态到内核态的切换(上下文切换)依旧有四次,同时需要注意在进行这种内存映射的时候,有可能会出现并发线程操作同一块内存区域而导致的严重的数据不一致问题,所以需要进行合理的并发编程来解决这些问题。
Java 中的 零 copy
Java中的零拷贝(Zero-copy)是指通过避免数据在不同内存区域之间的重复拷贝,从而提高数据传输的效率和性能的一种技术。在传统的数据传输过程中,数据需要从应用程序的内存中拷贝到操作系统内核的缓冲区中,然后再从操作系统内核的缓冲区中拷贝到网络设备的缓冲区中,最后再通过网络设备传输到远程设备的缓冲区中,这个过程中会涉及到多次的数据拷贝操作,这些拷贝操作会占用大量的CPU时间和内存带宽,从而影响系统的性能和效率。
具体实现
Java中的零拷贝技术通过使用直接内存缓冲区(Direct ByteBuffer)和Java NIO(New IO)库来实现。直接内存缓冲区是一种特殊的缓冲区,它可以在Java应用程序的堆内存之外分配内存,这个内存区域可以被操作系统直接访问,从而避免了数据的多次拷贝。Java NIO库是Java提供的一种基于通道和缓冲区的IO操作方式,它可以通过零拷贝技术来提高数据传输的性能和效率。
下面是一个简单的Java程序,演示了如何使用零拷贝技术来实现文件的快速复制:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class ZeroCopyDemo {
public static void main(String[] args) {
try {
// 打开输入文件和输出文件的通道
FileInputStream input = new FileInputStream("input.txt");
FileOutputStream output = new FileOutputStream("output.txt");
FileChannel inputChannel = input.getChannel();
FileChannel outputChannel = output.getChannel();
// 分配直接内存缓冲区
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
// 从输入通道读取数据,并直接写入输出通道
while (inputChannel.read(buffer) != -1) {
buffer.flip();
outputChannel.write(buffer);
buffer.clear();
}
// 关闭通道和文件流
inputChannel.close();
outputChannel.close();
input.close();
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个程序中,我们首先打开输入文件和输出文件的通道,并分配一个直接内存缓冲区。然后,我们从输入通道读取数据,并将数据直接写入输出通道中,这个过程中只涉及到一次数据拷贝操作,从而提高了程序的性能和效率。最后,我们关闭通道和文件流。
需要注意的是,零拷贝技术并不是适用于所有的场景,它主要适用于大数据量的数据传输,例如文件的快速复制和网络数据的传输等。在一些小数据量的场景中,零拷贝技术可能会带来额外的性能开销,需要根据具体的场景来选择是否使用零拷贝技术。