【HBZ】高性能zeroCopy零拷贝与普通IO差距与原理

简介

  1. 随着IO不断地发展,无论哪种拷贝方式,DMA从磁盘拷贝数据到内核缓冲区,都会拷贝多一些数据, 不会只拷贝用户态的指定size的数据,而是会将目标数据的临近数据也都拷贝到内核缓冲区,以便下次IO操作可以直接从内核缓冲区获取,无需再次走磁盘

普通IO的拷贝流程

  1. 普通的IO拷贝,要经历4次CPU上下文切换 与 4次拷贝操作
  2. 内核缓冲区拷贝数据到用户缓冲区是用户态指令是多少size,就拷贝多少size回去
  3. 每次拷贝的数据都恰好是需要的数据,因此每次需要查询数据时,都要走一遍整个过程,所以性能非常慢

普通IO-Buffered的拷贝流程

  1. buffered方式为什么比普通IO更快?
    答:buffered在内核缓冲区拷贝到用户缓冲区的时候,会将目标数据 及 目标临近的数据一同拷贝到用户缓冲区,这样下一次如果查询数据命中临近的数据,就不会在走内核态,所以会更快一些

零拷贝-mmap流程

  1. mmap方式的零拷贝会经历(4次上下问切换 与 3次拷贝)
  2. 由图可见, 内核缓冲区不会拷贝数据到用户缓冲区,而且用户 与 内核缓冲区共同指向一块虚拟内存地址,而数据会保存在虚拟内存所对应的物理内存地址中。并且用户态向socket缓冲区发起指令也不需要携带数据拷贝,而真正的数据拷贝是socket缓冲区直接从内核缓冲区中拷贝数据,所以减少了两次拷贝,在内核缓冲区与socket缓冲区又追加了一层拷贝,总体少了一次拷贝

零拷贝-sendfile流程

  1. sendfile()方式的零拷贝经历(2次切换,2次拷贝)
  2. 注意: 第三步拷贝偏移量本质也是拷贝,但拷贝的是内存偏移量,数据量极小,所以忽略不计
  3. 注意2:sendfiel()这种方式仅适用于本地相互拷贝,不适用于网络IO
  4. socket缓冲区获取数据,是从一块物理内存中直接获取,而这块物理内存是【内核缓冲区】 和 【socket缓冲区】共同映射的地址,因此数据是通过内存地址直接获取,而并非拷贝一份,所以只有2次拷贝

java代码实现4中拷贝

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

// 
普通IO
    @Test
    public void publicIo() throws FileNotFoundException {

        try (InputStream inputStream = new FileInputStream("D:\\zeroCopyTest\\zeroCopyFile.zip");
             OutputStream outputStream = new FileOutputStream("D:\\zeroCopyTest\\zeroCopyOutstream.zip");){

            byte[] bytes = new byte[1024];
            int  len;

            long start = System.currentTimeMillis();
            while ((len = inputStream.read(bytes)) != -1){
                outputStream.write(bytes,0,len);
            }
            long end = System.currentTimeMillis();

            System.out.println("耗费时间: " + (end - start));

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

// 
普通io-buffered
    @Test
    public void bufferIo() throws FileNotFoundException {

        try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream("D:\\zeroCopyTest\\zeroCopyFile.zip")) ;
             BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("D:\\zeroCopyTest\\zeroCopyOutstream.zip"));){

            byte[] bytes = new byte[1024];
            int  len;

            long start = System.currentTimeMillis();
            while ((len = inputStream.read(bytes)) != -1){
                outputStream.write(bytes,0,len);
            }
            long end = System.currentTimeMillis();

            System.out.println("耗费时间: " + (end - start));

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

// 零拷贝-mmap
    @Test
    public void mmapCopyFile(){

        long start = System.currentTimeMillis();
        try(
                FileChannel inputChannel = new FileInputStream("D:\\zeroCopyTest\\zeroCopyFile.zip").getChannel();
                FileChannel outputChannel = new RandomAccessFile("D:\\zeroCopyTest\\zeroCopyOutstream.zip","rw").getChannel();
        ){
            long size = inputChannel.size();

            MappedByteBuffer mapInBuffer = inputChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
            MappedByteBuffer mapOutBuffer = outputChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);

            byte[] buffer = new byte[1024]; // 设置数组大小为1024
            int len;

            while ((len = inputChannel.read(ByteBuffer.wrap(buffer))) != -1) {
                byte b = mapInBuffer.get(len);
                mapOutBuffer.put(b);
            }
            long end = System.currentTimeMillis();
            System.out.println("耗费时间: " + (end - start));

        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

// 零拷贝-sendfile()
 @Test
    public void sendfileCopyFile(){
        long start = System.currentTimeMillis();
        try(
                FileChannel inputChannel = new FileInputStream("D:\\zeroCopyTest\\zeroCopyFile.zip").getChannel();
                FileChannel outputChannel = new FileOutputStream("D:\\zeroCopyTest\\zeroCopyOutstream.zip").getChannel();
        ){

            // 方式1: 针对小于2GB文件
            // 实际拷贝的大小(只会拷贝2GB, inputChannel.size() = 3GB, 那realSize也就 = 2GB)
            //  参数1: 从哪里开始拷贝, 参数2: 拷贝多少个字节, 参数3: 拷贝到哪里去
//            long realSize = inputChannel.transferTo(0, inputChannel.size(), outputChannel);


            // 方式2: 针对大于2GB文件, 分多次读写
            // 获取文件总大小
            long size = inputChannel.size();
            for(long left = size; left > 0;){

                // 返回真实拷贝的大小
                // 这里position起始拷贝位置,改为size-left(即总大小 - 剩余大小的位置), 参数2: left就是拷贝多少大小
                long transferSize = inputChannel.transferTo(size - left, left, outputChannel);
                // 计算出还剩余的大小
                left -= transferSize;

            }
            long end = System.currentTimeMillis();
            System.out.println("耗费时间: " + (end - start));

        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }
相关推荐
汪洪墩10 分钟前
【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换
开发语言·javascript·python·ecmascript·webgl·cesium
Yvemil716 分钟前
《开启微服务之旅:Spring Boot 从入门到实践》(三)
java
Anna。。17 分钟前
Java入门2-idea 第五章:IO流(java.io包中)
java·开发语言·intellij-idea
居居飒17 分钟前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
.生产的驴39 分钟前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
我曾经是个程序员40 分钟前
鸿蒙学习记录
开发语言·前端·javascript
爱上语文41 分钟前
宠物管理系统:Dao层
java·开发语言·宠物
羊小猪~~1 小时前
前端入门之VUE--ajax、vuex、router,最后的前端总结
前端·javascript·css·vue.js·vscode·ajax·html5
王ASC1 小时前
SpringMVC的URL组成,以及URI中对/斜杠的处理,解决IllegalStateException: Ambiguous mapping
java·mvc·springboot·web
是小崔啊1 小时前
开源轮子 - Apache Common
java·开源·apache