参考链接:https://www.leahy.club/archives/zero-copy-pricinple
零拷贝是一种网络编程中的性能优化方式。
传统的Java I/O编程中的网络文件传输会涉及到多次的用户态和内核态之间的切换以及数据从硬盘到内核缓存区和用户缓存区的拷贝。零拷贝技术主要就是减少拷贝次数(并不是一次都不拷贝,而是减少CPU拷贝的次数,尽量使用DMA拷贝)和减少状态的切换。
零拷贝的实现主要有mmap和sendFile。
传统的I/O模型:
从上图中可以看出,传统的I/O模型的主要过程是:
从用户态切换到内核态、使用DMA将硬盘中的数据拷贝到内核中的kernel buffer、数据从kernel buffer拷贝到user buffer、从内核态切换到用户态。这些步骤是本地的I/O过程,可以使用BIO或者NIO等。我们讨论I/O模型的时候主要考察的范围就是这个过程,不包括下面的步骤。可以详细的阅读4中的第5小节(Linux中的select、poll和epoll)。
用户态准备好要发送的数据、从用户态切换到内核态、将数据拷贝到socket buffer、数据从socket buffer通过DMA方式拷贝到具体的网络硬件发送出去。这个也是一种I/O过程,称之为网络I/O。讨论I/O模型的时候不以这个为案例。
需要4次切换和2次CPU拷贝。
mmap优化:
mmap 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。如下图:
这里是将内核的kernel buffer与user buffer进行共享。从而减少了一次CPU拷贝。
需要4次切换和1次CPU拷贝。
sendFile:
Linux 2.1 版本提供了sendFile 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换。
Linux2.4进行了优化:,避免了从内核缓冲区拷贝到Socket buffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。
虽然还是需要一次CPU拷贝,但是拷贝的数据非常少,仅仅拷贝一些copy的desc信息,消耗CPU资源很低,可以忽略不计。
对零拷贝的再次理解:
- 我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有kernel buffer 有一份数据)。
- 零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的CPU 缓存伪共享以及无CPU 校验和计算。
mmap VS sendFile
- mmap 适合小数据量读写,sendFile 适合大文件传输。
- mmap 需要4 次上下文切换,3 次数据拷贝(包括CPU拷贝和DMA拷贝);sendFile 需要3 次上下文切换,最少2 次数据拷贝。
- sendFile 可以利用DMA 方式,减少CPU 拷贝,mmap 则不能(必须从内核拷贝到Socket 缓冲区)。