[java network programming] zero copy

Introduction

Zero copy is a technology that prevents the CPU from copying data from one storage to another. Various zero-copy technologies for device drivers, file systems, and network protocol stacks in the operating system have greatly improved the performance of specific applications and enabled these applications to use system resources more effectively. This performance improvement is achieved by allowing the CPU to perform other tasks while the data copy is in progress.

1. Traditional file sending

   while((n = read(diskfd, buf, BUF_SIZE)) > 0)
       socket.getOutputStream().write(sockfd, buf , n);

There is no way to transfer data directly between applications and disks.

The whole process is: the application program initiates a read request to the operating system, then first the data in the disk will be read into the kernel address space, and then the data in the kernel address space will be copied to the user address space (actually JVM memory ), and finally read this data into the application.

In the above figure, a total of four data copies have been generated, and multiple switching between the user state and the kernel state is the main reason for the low efficiency.

java code


public static void test1() throws Exception {
        // 利用通道完成文件的复制(非直接缓冲区)
        FileInputStream fis = new FileInputStream("a.txt");
        FileOutputStream fos = new FileOutputStream("b.txt");
        // 获取通道
        FileChannel fisChannel = fis.getChannel();
        FileChannel foschannel = fos.getChannel();

        // 通道没有办法传输数据,必须依赖缓冲区
        // 分配指定大小的缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        // 将通道中的数据存入缓冲区中
        while (fisChannel.read(byteBuffer) != -1) {  // fisChannel 中的数据读到 byteBuffer 缓冲区中
            byteBuffer.flip();  // 切换成读数据模式
            // 将缓冲区中的数据写入通道
            foschannel.write(byteBuffer);
            byteBuffer.clear();  // 清空缓冲区
        }
        foschannel.close();
        fisChannel.close();
        fos.close();
        fis.close();
    }

2. Use mmap+write to send

buf = mmap(file, len);
write(sockfd, buf, len);

mmap()The system call function will directly " map "  the data in the kernel buffer to the user space, so that the operating system kernel and user space do not need to perform any data copy operations.

 Using mmap instead of read can obviously reduce one copy

java code

public static void test2() throws Exception {
    // 使用直接缓冲区完成文件的复制(内存映射文件)
    /**
         * 使用 open 方法来获取通道
         * 需要两个参数
         * 参数1:Path 是 JDK1.7 以后给我们提供的一个类,代表文件路径
         * 参数2:Option  就是针对这个文件想要做什么样的操作
         *      --StandardOpenOption.READ :读模式
         *      --StandardOpenOption.WRITE :写模式
         *      --StandardOpenOption.CREATE :如果文件不存在就创建,存在就覆盖
         */
    FileChannel inChannel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.READ);
    FileChannel outChannel = FileChannel.open(Paths.get("c.txt"), StandardOpenOption.WRITE,
                                              StandardOpenOption.READ, StandardOpenOption.CREATE);

    /**
         * 内存映射文件
         * 这种方式缓冲区是直接建立在物理内存之上的
         * 所以我们就不需要通道了
         */
    MappedByteBuffer inMapped = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
    MappedByteBuffer outMapped = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

    // 直接对缓冲区进行数据的读写操作
    byte[] dst = new byte[inMapped.limit()];
    inMapped.get(dst);  // 把数据读取到 dst 这个字节数组中去
    outMapped.put(dst); // 把字节数组中的数据写出去

    inChannel.close();
    outChannel.close();
}

3. Use sendfile

In the Linux kernel version 2.1, a special system call function for sending files is provided  sendfile(), and the function form is as follows:

Replace the previous  read() and  write() these two system calls, reduce one system call, and reduce the overhead of 2 context switches

java code

public static void test3() throws Exception {
    /**
         * 通道之间的数据传输(直接缓冲区的方式)
         * transferFrom
         * transferTo
         */
    FileChannel inChannel = FileChannel.open(Paths.get("a.txt"), StandardOpenOption.READ);
    FileChannel outChannel = FileChannel.open(Paths.get("d.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE,
                                              StandardOpenOption.CREATE);
    inChannel.transferTo(0, inChannel.size(), outChannel);
    // 或者可以使用下面这种方式
    //outChannel.transferFrom(inChannel, 0, inChannel.size());
    inChannel.close();
    outChannel.close();
}

Guess you like

Origin blog.csdn.net/sumengnan/article/details/125098896