原文出自知乎:Java中的零拷贝
Java中的零拷贝
先提出两个问题:
- IO过程中,哪些步骤进行了拷贝?哪些地方零拷贝?
- Java支持哪些零拷贝?
带着这俩问题,我们一起来看下面的探究。
哪里听说过零拷贝?真的0次拷贝吗?
相信大家伙在以往的学习中,或多或少在下面这些组件、框架中有听说过零拷贝 (Zero-Copy)?
Kafka Netty rocketmq nginx apache
什么是零拷贝?
零拷贝(英语: Zero-copy) 技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
- 零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率
- 零拷贝技术减少了用户进程地址空间和内核地址空间之间因为上:下文切换而带来的开销
可以看出没有说不需要拷贝,只是说减少冗余[不必要]的拷贝。
LinuxI/O机制及零拷贝介绍
IO中断与DMA
IO中断,需要CPU响应,需要CPU参与,因此效率比较低。
用户进程需要读取磁盘数据,需要CPU中断,发起IO请求,每次的IO中断,都带来CPU的上下文切换。
因此出现了——DMA。
DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于CPU 的大量中断负载。 DMA控制器,接管了数据读写请求,减少CPU的负担。这样一来,CPU能高效工作了。 现代硬盘基本都支持DMA。
Linux IO流程
实际因此IO读取,涉及两个过程: 1、DMA等待数据准备好,把磁盘数据读取到操作系统内核缓冲区; 2、用户进程,将内核缓冲区的数据copy到用户空间。 这两个过程,都是阻塞的。
传统数据传送
比如:读取文件,再用socket发送出去 传统方式实现: 先读取、再发送,实际经过1~4四次copy。
buffer = File.read
Socket.send(buffer)
1、第一次:将磁盘文件,读取到操作系统内核缓冲区; 2、第二次:将内核缓冲区的数据,copy到application应用程序的buffer; 3、第三步:将application应用程序buffer中的数据,copy到socket网络发送缓冲区(属于操作系统内核的缓冲区); 4、第四次:将socket buffer的数据,copy到网卡,由网卡进行网络传输。
传统方式,读取磁盘文件并进行网络发送,经过的四次数据copy是非常繁琐的。实际IO读写,需要进行IO中断,需要CPU响应中断(带来上下文切换),尽管后来引入DMA来接管CPU的中断请求,但四次copy是存在“不必要的拷贝”的。
重新思考传统IO方式,会注意到实际上并不需要第二个和第三个数据副本。应用程序除了缓存数据并将其传输回套接字缓冲区之外什么都不做。相反,数据可以直接从读缓冲区传输到套接字缓冲区。
显然,第二次和第三次数据copy 其实在这种场景下没有什么帮助反而带来开销,这也正是零拷贝出现的背景和意义。
传统数据传送所消耗的成本:4次拷贝,4次上下文切换。 4次拷贝,其中两次是DMA copy,两次是CPU copy。如下图所示 拷贝是个IO过程,需要系统调用。
注意一点的是 内核从磁盘上面读取数据 是 不消耗CPU时间的,是通过磁盘控制器完成;称之为DMA Copy。 网卡发送也用DMA。
零拷贝的出现
目的:减少IO流程中不必要的拷贝 零拷贝需要OS支持,也就是需要kernel暴露api。虚拟机不能操作内核,
Linux支持的(常见)零拷贝
一、mmap内存映射
data loaded from disk is stored in a kernel buffer by DMA copy. Then the pages of the application buffer are mapped to the kernel buffer, so that the data copy between kernel buffers and application buffers are omitted.
DMA加载磁盘数据到kernel buffer后,应用程序缓冲区(application buffers)和内核缓冲区(kernel buffer)进行映射,数据再应用缓冲区和内核缓存区的改变就能省略。
mmap内存映射将会经历:3次拷贝: 1次cpu copy,2次DMA copy; 以及4次上下文切换