netty4源码阅读与分析---零拷贝

在说零拷贝之前,我们先来看下传统的读写方式是怎样的,如下图:netty4源码阅读与分析---零拷贝_第1张图片

读取数据时,请求会把读操作委托给内核,由内核与磁盘进行交互。数据会从磁盘拷贝到内核的缓存区中,这个copy动作由DMA完成,整个过程中基本上不消耗CPU,但是应用程序想拿到信息得从内核缓冲区获取,经过cpu copy的动作,将数据从内核缓冲区中拷贝到应用缓冲区中,这个copy动作是需要消耗CPU的。

写数据时,应用想将数据经过内核,将数据先从应用缓冲区中copy(cpu copy)到内核的缓冲区中,然后再通过DMA copy到磁盘。不论读写,上面的方式都会有两次copy,涉及到用户态与内核态的数据交换。

对于操作系统而言,零拷贝指避免在 用户态(User-space) 与 内核态(Kernel-space) 之间来回拷贝数据,而netty的另拷贝都是基于用户态的操作,更多的是在优化数据操作上。其零拷贝主要体现在以下几个方面:

a,Netty的接收和发送ByteBuffer采用directe buffer,使用堆外内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(heapbuffer)进行Socket读写时会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,多了一次缓冲区的内存拷贝

b,Netty 提供了 CompositeByteBuf 类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个byteBuf间的拷贝。假设我们有两个byteBuf,header和body,现在我们想把它合成一个ByteBuf使用,通常的做法是:

ByteBuf header = Unpooled.buffer(2);
        header.writeChar('a');
        ByteBuf body = Unpooled.buffer(4);
        body.writeChar('b');

        
        ByteBuf allBuf = Unpooled.buffer(header.readableBytes()+body.readableBytes());
        allBuf.writeBytes(header);
        allBuf.writeBytes(body);
这里会有两次额外的数据拷贝,而利用CompositeByteBuf,我们可以实现零拷贝
CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
        compositeByteBuf.addComponents(true,header,body);

c,netty中还可以通过wrap操作来实现零拷贝

举个例子,现在我们有个byte数组,我们希望变成一个byteBuf对象,方便操作,通常的做法如下:

byte[] b = new byte[10];
        ByteBuf byteBuf = Unpooled.buffer(10);
        byteBuf.writeBytes(b);

显然这里存在一次额外的拷贝操作,而使用wrap操作则可以避免这次额外的拷贝

byteBuf = Unpooled.wrappedBuffer(b);

d,通过slice的方式实现零拷贝

其实就是对bytebuf进行分片,本质上底层还是一个byte数组,对其进行了封装,分片后的bytebuf都持有相应的readerIndex和writeIndex,能够从原来的byte数组中获取或者写入正确的数据。







你可能感兴趣的:(java,NIO)