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的一段代码,看起来非常简单,然而其中包含着非常多的操作,下面的图表明了这段代码发生的事情

read write

其中至少包含了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操作,进行直接复制的操作来避免用户态的切换

如下图

1556981497491

  1. sendFile将硬件数据复制到old fd关联的内核缓冲区中
  2. 将数据复制到new fd关联的内核缓冲区
  3. DMA将内核缓冲区复制到协议引擎
  4. 协议引擎进行数据写入

一共进行3次数据复制,2次上下文切换

完全消除不必要复制的sendFile

这个方式还可以进行优化,其中数据复制到新文件描述符的操作不是必要的,可以通过内存地址的方式寻址到第一次复制的数据.即关联old fd和new fd即可

1556982326290

  1. sendFile将硬件数据复制到内核缓冲区中
  2. 将old fd和new fd进行关联
  3. 将old fd中的数据写入到硬件

在Java中使用FileChanneltransferFrom函数和transferTo函数实现

上述使用sendFile的方式仅适用于不需要操作数据的简单复制,如果需要操作数据,最优的方式是mmap的方式

netty的ZeroCopy

与操作系统的ZeroCopy,netty由于本身不仅仅是数据的复制,大部分情况下,netty都需要进行数据的操作,因此netty更加关注的是避免复制.
netty提供了许多种避免复制的方式,但本质上都是一样的,即通过视图的方式进行,如果熟悉数据库的话马上就能够理解意思了.视图不是真实存在的数据,而是对真实数据的一种访问方式.另外对于不需要操作数据的情况,jdk本身就已经提供了transferTotransferFrom函数,netty进行了一点封装以便ByteBuf使用

多个buffer的合并视图

CompositeByteBuf

JDK的ByteBuffer未找到类似的操作

netty使用compositeByteBuf.addComponents相关的函数进行操作

原生类型的视图

JDK通过ByteBuffer.wrap相关函数进行操作

netty使用Unpooled.wrappedBuffer相关的函数进行操作

单个buffer的部分视图

JDK使用ByteBuffer.slice()函数进行操作

netty使用ByteBuf.slice相关的函数进行操作

直接复制

netty使用FileRegion,实际上底层还是使用的JDK的transfer

发布了27 篇原创文章 · 获赞 6 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/l1161558158/article/details/89820412
今日推荐