前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >netty与nio中的zero copy

netty与nio中的zero copy

作者头像
山行AI
发布2019-09-17 17:41:47
9010
发布2019-09-17 17:41:47
举报
文章被收录于专栏:山行AI山行AI

首先看下维基百科上对zero-copy的介绍:Zero-copy versions of operating system elements, such as device drivers, file systems, and network protocol stacks, greatly increase the performance of certain application programs and more efficiently utilize system resources. Performance is enhanced by allowing the CPU to move on to other tasks while data copies proceed in parallel in another part of the machine. Also, zero-copy operations reduce the number of time-consuming mode switches between user space and kernel space. System resources are utilized more efficiently since using a sophisticated CPU to perform extensive copy operations, which is a relatively simple task, is wasteful if other simpler system components can do the copying.

前言

维基百科中有介绍,在传统的方式里面,读取并通过网络发送一个文件在每次读或者写时都需要两次数据拷贝和两次上下文切换。其中的一次数据拷贝是通过CPU来完成的。通过zero-copy来传送文件可以将上下文切换减少到两次并且 可以消除所有的cpu数据拷贝。原文如下:

代码语言:javascript
复制
As an example, reading a file and then sending it over a network the traditional way requires two data copies and two context
switches per read/write cycle. One of those data copies uses the CPU. 
Sending the same file via zero copy reduces the context switches to two and eliminates all CPU data copie

传统I/O

传统I/O通过两条系统指令read、write来完成数据的读取和传输操作。

  1. 第一次拷贝为DMA copy: hard drive ——> kernel buffer。通过DMA引擎将文件中的数据从磁盘上读取到内核空间缓冲区,导致用户空间到内核空间的上下文切换(第一次从系统read上下文切换)。
  2. 第二次拷贝为CPU copy: kernel buffer ——> user buffer 将内核空间缓冲区的数据拷贝到用户空间缓冲区。系统调用的返回又会导致一次内核空间到用户空间的上下文切换(第二次read上下文切换)。
  3. 第三次拷贝为CPU copy: user buffer ——> socket buffer 将用户空间缓冲区中的数据拷贝到内核空间中与socket相关联的缓冲区中。导致用户空间到内核空间的上下文切换(第一次向系统write上下文切换)。
  4. 第四次拷贝为DMA copy: socket buffer ——> protocol engine 通过DMA引擎将内核缓冲区中的数据传递到协议引擎,导致内核空间到用户空间的再次上下文切换(第二次write上下文切换)。

可以看到,在每次读或者写时都有两次data copy和两次上下文的切换。

Linux 2.1内核开始引入了sendfile函数

sendfile只使用了一条指令就完成了数据的读写操作。

  1. 第一次拷贝是从磁盘到kernel buffer,发生第一次上下文切换;
  2. 第二次拷贝是从kernel buffer到socket buffer;
  3. 第三次拷贝是从socker buffer到protocol engine,发生第二次上下文切换。

总共有三次data copy和两次上下文切换,较传统方式有很大改进。

在Java NIO包中提供了零拷贝机制对应的API,即FileChannel.transferTo()方法。不过FileChannel类是抽象类,transferTo()也是一个抽象方法,因此还要依赖于具体实现。

代码语言:javascript
复制
public void transferTo(long position, long count, WritableByteChannel target);

底层调用的方法:
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

Linux 2.4版本的sendfile

从Linux 2.4版本开始,操作系统底层提供了带有scatter/gather的DMA来从内核空间缓冲区中将数据读取到协议引擎中。这样一来待传输的数据可以分散在存储的不同位置上, 而不需要在连续存储中存放。那么从文件中读出的数据就根本不需要被拷贝到socket缓冲区中去,只是需要将缓冲区描述符添加到socket缓冲区中去,DMA收集操作会根据缓冲区描述符中的 信息将内核空间中的数据直接拷贝到协议引擎中。

  1. 第一次拷贝是从磁盘到kernel buffer,会发生一次上下文切换。
  2. 第二次拷贝是从kernel buffer到socket buffer(DMA gather copy根据socket缓冲区中描述符提供的位置和偏移量信息直接将内核空间缓冲区中的数据拷贝到协议引擎上),会进行第二次上下文切换。

注意:在这里,所有的cpu copy都被消除掉了,而且上下文切换被减少到两次。

mmap

mmap(内存映射)是一个比sendfile昂贵但优于传统I/O的方法。

可见与上面的传统IO方式比较,减少了一次cpu copy。

NIO框架中提供了MappedByteBuffer用来支持mmap。它与常用的DirectByteBuffer一样,都是在堆外内存分配空间。相对地,HeapByteBuffer在堆内内存分配空间。

应用

  1. 在kafka的PlaintextTransportLayer的对应方法中,就是直接调用了FileChannel.transferTo()方法;
  2. 在spark中以BypassMergeSortShuffleWriter为例,它最终是调用了通用工具类Utils中的copyFileStreamNIO()方法。
  3. Netty的文件传输调用FileRegion包装的transferTo方法,可以直接将文件缓冲区的数据发送到目标Channel,避免通过循环write方式导致的内存拷贝问题。FileRegion底层调用NIO FileChannel的transferTo函数。

netty的其他zero copy

通过CompositeByteBuf实现零拷贝

CompositeByteBuf可以把需要合并的多个bytebuf组合起来,对外提供统一的readIndex和writerIndex。但在CompositeByteBuf内部, 合并的多个ByteBuf都是单独存在的,CompositeByteBuf 只是逻辑上是一个整体。CompositeByteBuf里面有个Component数组,聚合的bytebuf都放在Component数组里面,最小容量为16。

通过wrap操作实现零拷贝

通过Unpooled.wrappedBuffer方法将bytes包装为一个UnpooledHeapByteBuf对象, 而在包装的过程中, 不会有拷贝操作的,即生成的ByteBuf对象是和bytes数组共用了同一个存储空间,对bytes的修改也就是对ByteBuf对象的修改。

通过slice操作实现零拷贝

用slice方法产生header和body的过程是没有拷贝操作的,header和body对象在内部其实是共享了byteBuf存储空间的不同部分而已 。

参考

  • https://en.wikipedia.org/wiki/Zero-copy
  • https://www.jianshu.com/p/e76e3580e356
  • https://www.jianshu.com/p/193cae9cbf07
  • https://www.zhihu.com/question/57374068
  • https://www.jianshu.com/p/e488c8ee5b57
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发架构二三事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 传统I/O
  • Linux 2.1内核开始引入了sendfile函数
  • Linux 2.4版本的sendfile
  • mmap
  • 应用
  • netty的其他zero copy
    • 通过CompositeByteBuf实现零拷贝
      • 通过wrap操作实现零拷贝
        • 通过slice操作实现零拷贝
        • 参考
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档