首先,我想介绍一下传统上零拷贝的含义,事实上,Netty中的零拷贝又有所区别,最后再介绍Netty中零拷贝的四种实现方式。
计算机早期的数据传输方式:
整个流程:
从整个数据传输流程可以看出:CPU是一直被占用的,无法处理其他任务,性能比较差。
接口模块
早期阶段的结构中,每个部件都有单独连线,线多而且不容易扩展,称为分散连接;之后,引入了总线连接,将I/O设备连接到一条总线上,形成了公共传输通道。
这种模式下,数据传输采用了一种程序中断的方式,相当于将早期CPU不断询问I/O设备的过程去掉,流程变为:
DMA(Direct Memory Access)直接内存存取
上述方式在一定程度上提高了CPU的利用率,但在程序中断时,CPU仍然被占用,为此,引入了DMA原理,主存和I/O设备之间有一条数据通路,如此一来,主存和I/O设备之间传输数据就不用中断CPU了。
传统的发送数据要经过四个阶段,两次DMA。两次CPU中断,,共有四次拷贝,四次上下文切换,占用了两次CPU:
从整个流程可以看出,传统传输数据的优缺点:
优点:开发成本低;
缺点:多次上下文切换,多次占用CPU资源,性能不好。
零拷贝
传统意义上的零拷贝相当于去掉上述流程的第2和3个阶段,计算机在网络上发送文件时,不需要将数据从内核空间拷贝到用户空间,而是由内核空间直接传输到网络上。Java NIO中文件通道的transferTo()方法实现了OS的sendfile,利用channel完成了零拷贝。
整个传输流程如下:
整个流程并未将数据从内核空间拷贝到用户空间,即为零拷贝。虽然内存拷贝减少到3次,但仍要中断CPU来复制数据,原因在于DMA需要知道内存地址才能发送数据。在Linux2.4内核中进行了改进,将内核缓存(kernel buffer)中对应的数据描述信息(偏移量等)记录到相应的socket buffer中,形成了以下传输过程:
由于上述过程不需要CPU参与拷贝过程,效率是最好的,而Netty就是采用这种方式。
Netty的零拷贝与上述意义上的零拷贝不太一样,更多的是用户态的(Java层面),其零拷贝更多的是偏向优化数据操作的层面。
Netty的零拷贝主要体现在:
1、CompositeByteBuf,将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了各个ByteBuf之间的拷贝。
使用方式:
CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
compositeByteBuf.addComponents(true, ByteBuf1, ByteBuf1);
注意: addComponents第一个参数必须为true,那么writeIndex才不为0,才能从compositeByteBuf中读到数据。
2、wrapedBuffer()方法,将byte[]数组包装成ByteBuf对象。
byte[] bytes = data.getBytes();ByteBuf byteBuf = Unpooled.wrappedBuffer(bytes);
Unpooled.wrappedBuffer(bytes)就是进行了byte[]数组的包装工作,过程中不存在内存拷贝。
即包装出来的ByteBuf和byte[]数组指向了同一个存储空间。因为值引用,所以bytes修改也会影响 byteBuf 的值。
3、ByteBuf的分割,slice()方法。将一个ByteBuf对象切分成多个ByteBuf对象。
ByteBuf directByteBuf = ByteBufAllocator.DEFAULT.directBuffer(1024);ByteBuf header = directByteBuf.slice(0,50);ByteBuf body = directByteBuf.slice(51,1024);
header和body两个ByteBuf对象实际上还是指向directByteBuf的存储空间。
具体的四种实现Netty的零拷贝的实现请参照:https://segmentfault.com/a/1190000007560884
参考博文:
https://www.jianshu.com/p/82d7269521a3
https://infoq.cn/article/netty-high-performance
https://www.codercto.com/a/23502.html