飞道的博客

聊聊零拷贝?

320人阅读  评论(0)

什么是零拷贝

零拷贝是指计算机在执行IO操作的时候,CPU不需要将数据从一个存储区复制到另一个存储区,进而减少上下文切换以及CPU拷贝的时间,这是一种IO操作优化技术

零拷贝不是没有拷贝数据,而是减少用户态,内核态的切换次数和CPU拷贝次数,目前实现零拷贝的主要三种方式分别是:

  • mmap + write
  • sendfile
  • 带有DMA收集拷贝功能的sendfile

mmap

虚拟内存把内核空间和用户空间的虚拟地址映射到同一个物理地址,从而减少数据拷贝次数,mmap技术就是利用了虚拟内存的这个特点,它将内核 中的读缓冲区与用户空间的缓冲区进行映射,所有的IO操作都在内核中完成

sendfile

sendfile是Linux2.1 版本之后内核引入的一个系统调用函数

sendfile表示在两个文件描述符之间传输数据,他是在操作系统内核中完成的,避免了数据从内核缓冲区和用户缓冲区之间的拷贝操作,因此可以用来实现零拷贝

在linux2.4版本之后,对sendfile进行了升级,引入了SG-DMA技术,可以直接从缓冲区中将数据读取到网卡,这样的话可以省去CPU拷贝

Java实现的零拷贝

mmap

在Java NIO 有一个ByteBuffer的子类MappedByteBuffer,这个类采用direct buffer 也就是内存映射的方式读写文件内容。这种方式直接调用系统底层的缓存,没有JVM和系统之间的复制操作,主要用户操作大文件

sendfile

FileChannel的transferTo()方法或者transferFrom()方法,底层就是sendfile()系统调用函数,实现了数据直接从内核的读缓冲区传输到套接字缓冲区,避免了用户态与内核态之间的数据拷贝

Netty的零拷贝

Netty的零拷贝主要体现在以下几个方面

  • slice
  • duplicate
  • CompositeByteBuf

slice

log 工具类

public class ByteBufUtil {
   
    // 打印
    public static void log(ByteBuf buf) {
   
        final int length = buf.readableBytes();
        int rows = length >> 16 + (length % 15 == 0 ? 0 : 1) + 4;
        StringBuilder str = new StringBuilder(rows * 80 * 2)
                .append("read index: ").append(buf.readerIndex())
                .append(" write index: ").append(buf.writerIndex())
                .append(" capacity: ").append(buf.capacity())
                .append(NEWLINE);
        appendPrettyHexDump(str, buf);
        System.out.println(str);
    }
}

slice

对原始的ByteBuf进行切片成多个ByteBuf,切片后的ByteBuf并没有发生内存复制,还是使用原始的ByteBuf内存,但是切片后的ByteBuf各自有独立的read,write指针

注意:

  • slice 不允许更改切片的容量,切片时设置的长度是多少就是多少,不允许扩容
  • 当我们释放原始 ByteBuf 内存之后,切片后的ByteBuf就不能再访问了

测试:

  • 首先创建一个ByteBuf,然后对其进行切片
  • 更改某一个切片查看原始ByteBuf是否更改
  • 原始数据跟着更改了说明内存地址没有发生改变

测试类

public static void main(String[] args) {
   
        System.out.println(32 / 16);
        System.out.println(32 >> 4);
        System.out.println("------------------------");
        // 创建ByteBuf
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(10);
        // 向 byteBuf 缓冲区写入数据
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < 5; i++) {
   
            str.append("nx");
        }
        byteBuf.writeBytes(str.toString().getBytes());


        // 打印当前 byteBuf
        ByteBufUtil.log(byteBuf);

        // 切片过程中并没有发生数据复制
        final ByteBuf slice = byteBuf.slice(0, 5);
        final ByteBuf slice1 = byteBuf.slice(5, 5);

        // 打印第一个切片
        ByteBufUtil.log(slice);
        // 打印第二个切片
        ByteBufUtil.log(slice1);

        slice.setByte(0, 'a');
        System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
        // 打印第一个切片
        ByteBufUtil.log(slice);
        // 打印原始数组
        ByteBufUtil.log(byteBuf);
    }

 

额外知识补充

  • / 等于 >>
  • × 等于 <<

转载:https://blog.csdn.net/qq_42582773/article/details/128350965
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场