原文出自知乎: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。虚拟机不能操作内核,