零拷贝

28 阅读2分钟

零拷贝基本介绍

  1. 零拷贝是网络编程的关键,很多性能优化都离不开
  2. 在Java中 常用的零拷贝有mmap(直接内存映射)和sendFile。

传统IO数据读写

比如下面这段代码:

File file = new File("test.txt");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
byte[] arr = new byte[(int) file.length()];
raf.read(arr);
Socket socket = new ServerSocket(8080).accept();
socket.getOutputStream().write(arr);

传统IO模型:

image-20240104091900362

DMA:direct memory access 直接内存拷贝(不经过CPU)

  1. 先通过DMA拷贝将数据拷贝到内核buffer
  2. 在把内核buffer用CPU拷贝到用户BUFFer,数据其实是在用户BUffer进行修改
  3. 修改完毕后,再用CPU拷贝到socketBuffer
  4. 再用DMA拷贝,拷贝到协议栈

总结:传统的IO经过了4次拷贝,3次切换。在传统IO里面拷贝的次数非常多。代价非常多。

内存映射优化mmap

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

示意图如下:

image-20240104092822626

  1. 先通过DMA拷贝将数据拷贝到内核buffer
  2. 由于有了mmap,数据可以再内核缓冲进行修改
  3. 修改完成后,在通过CPU拷贝到socketBuffer
  4. 在通过socketBuffer 进行DMA拷贝到协议栈

总结:mmp优化后,拷贝次数减少为3次,但是状态的切换依然是3次没有减少。

sendFile优化

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

示意图

image-20240104093329655

总结:拷贝次数为3次,状态切换为3次

所谓零拷贝不是没有拷贝,而是没有CPU拷贝,DMA拷贝是没有办法避免的

注意:Linux 在 2.4 版本中,做了一些修改,避免了从内核缓冲区拷贝到 Socketbuffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝

image-20240104094216577

  1. 先通过DMA拷贝将数据拷贝到内核buffer
  2. 数据可以直接通过DMA拷贝到协议栈 不经过socketbuffer(只是一些很少的数据拷贝到socketbuffer:一些数据的长度、offset。信息量很少、消耗低 可以忽略)

总结:拷贝次数变为2次

mmap和sendFile区别

  1. mmap 适合小数据量读写,sendFile 适合大文件传输。
  2. mmap 需要 4 次上下文切换,3 次数据拷贝;sendFile 需要 3 次上下文切换,最少 2 次数据拷贝。
  3. sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)