netty|ZeroCopy

操作系统的ZeroCopy 简单操作

byte[] buf=new byte[1024]; InputStream inputStream=new FileInputStream("in.txt"); OutputStream outputStream = new FileOutputStream("out.txt"); int read; while ((read=inputStream.read(buf))!=-1){ outputStream.write(buf,0,read); } inputStream.close(); outputStream.close();

这一段代码是一个经典的将in.txt文件复制到out.txt的一段代码,看起来非常简单,然而其中包含着非常多的操作,下面的图表明了这段代码发生的事情
netty|ZeroCopy
文章图片

其中至少包含了4次数据的复制,并且包含了多次用户空间和内核空间的切换.其中**DMA(Direct Memory Access)**叫做直接内存访问
  1. read将数据从通过DMA将硬件数据复制到old fd相关的内核缓冲区中
  2. 内核数据缓冲区的数据复制到用户空间
  3. 用户空间复制到new fd对应的内核缓冲区
  4. DMA将内核空间缓冲区数据复制到协议引擎,第四次复制
  5. 协议引擎进行数据写入
第一次不需要将DMA数据复制到fd关联的数据缓冲区的原因是,这个硬件本身就是通过fd查找到的.
write的时候需要复制到fd相关的内核缓冲区中,因为是另外一个fd
使用内存映射优化的操作 对于数据的复制,用户空间的数据其实是不需要的,因此可以针对这种问题进行优化,即不进行内核态到用户态的数据复制,即使用内存映射.
内存映射可以将内核空间中的内存块关联到用户空间,使得用户空间看起来在操作内核空间一样,底层是使用mmap函数
使用这个函数可以去除内核空间复制到用户空间的操作,但仍然需要从old fd复制到new fd空间
因此,数据的复制操作可以减少为3次.但用户态的切换次数仍然不变
在Java中使用MappedByteBuffer实现这个操作,实际上底层操作的是DirectByteBuffer
FileChannel channel=... MappedByteBuffer map=channel.map(FileChannel.MapMode.READ_WRITE,0,5); map.set(1,'b')

FileChannel可以来自于File和IO
使用sendFile的操作 最少用户态切换的sendFile操作
除上述操作之外,还可以使用内核2.1之后提供的sendFile操作,进行直接复制的操作来避免用户态的切换
如下图
netty|ZeroCopy
文章图片

  1. sendFile将硬件数据复制到old fd关联的内核缓冲区中
  2. 将数据复制到new fd关联的内核缓冲区
  3. DMA将内核缓冲区复制到协议引擎
  4. 协议引擎进行数据写入
一共进行3次数据复制,2次上下文切换
完全消除不必要复制的sendFile
这个方式还可以进行优化,其中数据复制到新文件描述符的操作不是必要的,可以通过内存地址的方式寻址到第一次复制的数据.即关联old fd和new fd即可
netty|ZeroCopy
文章图片

  1. sendFile将硬件数据复制到内核缓冲区中
  2. 将old fd和new fd进行关联
  3. 将old fd中的数据写入到硬件
在Java中使用FileChannel的transferFrom函数和transferTo函数实现
上述使用sendFile的方式仅适用于不需要操作数据的简单复制,如果需要操作数据,最优的方式是mmap的方式
netty的ZeroCopy 与操作系统的ZeroCopy,netty由于本身不仅仅是数据的复制,大部分情况下,netty都需要进行数据的操作,因此netty更加关注的是避免复制.
netty提供了许多种避免复制的方式,但本质上都是一样的,即通过视图的方式进行,如果熟悉数据库的话马上就能够理解意思了.视图不是真实存在的数据,而是对真实数据的一种访问方式.另外对于不需要操作数据的情况,jdk本身就已经提供了transferTo和transferFrom函数,netty进行了一点封装以便ByteBuf使用
多个buffer的合并视图 CompositeByteBuf
JDK的ByteBuffer未找到类似的操作
netty使用compositeByteBuf.addComponents相关的函数进行操作
原生类型的视图 JDK通过ByteBuffer.wrap相关函数进行操作
netty使用Unpooled.wrappedBuffer相关的函数进行操作
单个buffer的部分视图 JDK使用ByteBuffer.slice()函数进行操作
netty使用ByteBuf.slice相关的函数进行操作
直接复制 【netty|ZeroCopy】netty使用FileRegion,实际上底层还是使用的JDK的transfer

    推荐阅读