零拷贝
零拷贝
- 零拷贝是没有cpu拷贝, 从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有 kernel_buffer 有一份数据)。
- Java 程序中,常用的零拷贝有 mmap(内存映射) 和 sendFile;
- 零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,如更少的上下文切换,更少的 CPU 缓存伪共享以及无CPU校验和计算;
交互过程:
名词解释: user context:用户态;kernel context:内核态;DMA copy:直接内存拷贝;CPU copy:cpu拷贝
-
传统IO
传统IO如上图先将本地文件拷贝到kernel_buffer(内核缓冲区),在经过cup_copy拷贝到user_buffer,数据在user_buffer中进行修改,然后在由user_buffer拷贝到socket_buffer再由DMA_copy拷贝到protocol_engine(协议引擎/协议栈),共经过了4次copy和3次状态切换; -
mmap优化mmap 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户控件的拷贝次数。如上图先将本地文件拷贝到kernel_buffer(内核缓冲区),kernel_buffer可以共享数据到user_buffer,实现在user_buffer修改共享,再经过cup_copy拷贝到socket_buffer再由DMA_copy拷贝到protocol_engine(协议引擎/协议栈),共经过了3次copy和3次状态切换;
-
sendFile优化
Linux 2.1 版本 提供了 sendFile 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换,共经过了3次copy和2次状态切换;
Linux 在 2.4 版本中,做了一些修改,避免了从内核缓冲区拷贝到 Socket buffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。其中kernel_buffer到socket _buffer的cpu_copy信息很少,比如lenght、 offset ,消耗低可以忽略,共经过了2次copy和2次状态切换;
mmap 和 sendFile 的区别
- mmap 适合小数据量读写,sendFile 适合大文件传输;
- mmap需要3次上下文切换,3次数据拷贝;
- sendFile 需要2次上下文切换,最少2次数据拷贝;
- sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)
实例 :
客户端
package com.neei.nio.zeroCopy;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
/**
* @param
* @Author: AaNeei
* @Date: 2019/12/2 21:47
* @Description: 游学网
* @throws:
*/
public class NIOClient {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8888));
//得到一个文件channel
FileChannel fileChannel = new FileInputStream("xxx.mp4").getChannel();
//准备发送
long startTime = System.currentTimeMillis();
//transferTo 底层使用到零拷贝
//transfer默认大小8M
double maxSize = 8 * 1024 * 1024D;
//向上取整需要拷贝的次数
int num = (int) Math.ceil(fileChannel.size() / maxSize);
long transferCount = 0;
//循环copy大文本
for (int i = 0; i < num; i++) {
int count = (int) maxSize;
if (i == num - 1) {
count = (int) Math.ceil(fileChannel.size() % maxSize);
}
transferCount += fileChannel.transferTo((long) (i * maxSize), count, socketChannel);
}
System.out.println("发送的总的字节数 =" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime));
//关闭
fileChannel.close();
}
}
服务端
package com.neei.nio.zeroCopy;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* @param
* @Author: AaNeei
* @Date: 2019/12/2 21:39
* @Description: 游学网
* @throws:
*/
public class NIOServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(8888));
ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
int read = 0;
while (read != -1) {
try {
read = socketChannel.read(byteBuffer);
} catch (Exception e) {
break;
}
byteBuffer.rewind();
}
}
}
}
分析:
- 零拷贝相对于传统IO拷贝具有更高的效率,主要体现在减少cpu拷贝,同时也减少了用户态的切换,零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,更少的上下文切换,更少的 CPU 缓存伪共享以及无CPU校验和计算