三种零拷贝的实现以及对比

简介

本文主要是介绍几种零拷贝的实现示例,以及与最传统的做一个对比,看看在效率上到底有多大的提升,如果想了解几种零拷贝的底层原理实现的理论知识的话,可以看看下面这篇文章

链接: 深入理解零拷贝技术open in new window

好了,废话不多说直接干,本章例子是通过网络IO传输一个8M大小的文件,对比传输效率,由于服务端接收端不需要修改,所以我们先上服务端代码:

public static void main(String[] args) throws IOException {
    ServerSocket serverSocket = new ServerSocket(8080);
    System.out.println("服务端:等待连接");
    Socket accept = serverSocket.accept();
    System.out.println("服务端:" + accept.getRemoteSocketAddress() + "已连接");

    File file = new File("C:\\Users\\Administrator\\Desktop\\ioTest.txt");
    if(!file.exists()){
        file.createNewFile();
    }
    FileOutputStream fileOutputStream = new FileOutputStream(file);
    InputStream bufferedInputStream = accept.getInputStream();
    byte[] bytes = new byte[2048];
    int read;
    while ((read = bufferedInputStream.read(bytes,0,2048)) != -1) {
        fileOutputStream.write(bytes);
    }
    OutputStream outputStream = accept.getOutputStream();
    outputStream.write("接收完毕".getBytes());
    accept.shutdownOutput();

    fileOutputStream.close();
    outputStream.close();
    bufferedInputStream.close();
    accept.close();
}

传统实现

正常的socket传输,耗时:46ms

public static void normal() throws IOException {
        Socket socket = new Socket("127.0.0.1", 8080);
        OutputStream outputStream = socket.getOutputStream();
        InputStream inputStream = socket.getInputStream();

        long start = System.currentTimeMillis();
        File file = new File("C:\\Users\\Administrator\\Desktop\\222.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        byte[] bytes1 = new byte[2048];
        while (fileInputStream.read(bytes1, 0, 2048) != -1) {
            outputStream.write(bytes1);
        }
        socket.shutdownOutput();
        System.out.println("耗时:" + (System.currentTimeMillis() - start));

        byte[] bytes = new byte[1024];
        String message = "";
        int read;
        while ((read = inputStream.read(bytes)) != -1) {
            message += new String(bytes, 0, read);
        }
        System.out.println("服务端发来消息->" + message);

        inputStream.close();
        outputStream.close();
        socket.close();
    }

MMAP

MMAP原理就是建立了一个文件映射,划分了一个虚拟空间,往这个空间写数据,少了一次拷贝

缺点:空间有限

实践案例:RocketMq

耗时:32ms

public static void mmp() throws IOException {

        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));

        long start = System.currentTimeMillis();
        Path path = Paths.get("C:\\Users\\Administrator\\Desktop\\222.txt");
        FileChannel open = FileChannel.open(path, StandardOpenOption.READ);
        MappedByteBuffer map = open.map(FileChannel.MapMode.READ_ONLY, 0, open.size());
        socketChannel.write(map);
        socketChannel.shutdownOutput();
        System.out.println("耗时:" + (System.currentTimeMillis() - start));

        ByteBuffer allocate = ByteBuffer.allocate(1024);
        int read = socketChannel.read(allocate);
        if (read > 0) {
            allocate.flip();
            byte[] bytes = new byte[allocate.remaining()];
            allocate.get(bytes);
            System.out.println("服务端发来消息:" + new String(bytes));
        }

        socketChannel.close();
    }

transferTo

原理就是两个通道之间直接传输数据,根据系统支持程度,少了1-2次拷贝

缺点:局限于文件通道

实践案例:Netty、Kafka

耗时:18ms

public static void transferTo() throws IOException {
       SocketChannel socketChannel = SocketChannel.open();
       socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));

       long start = System.currentTimeMillis();
       Path path = Paths.get("C:\\Users\\Administrator\\Desktop\\222.txt");
       FileChannel open = FileChannel.open(path, StandardOpenOption.READ);
       long l = open.transferTo(0, open.size(), socketChannel);
       socketChannel.shutdownOutput();
       System.out.println("耗时:" + (System.currentTimeMillis() - start));

       ByteBuffer allocate = ByteBuffer.allocate(1024);
       int read = socketChannel.read(allocate);
       if (read > 0) {
           allocate.flip();
           byte[] bytes = new byte[allocate.remaining()];
           allocate.get(bytes);
           System.out.println("服务端发来消息:" + new String(bytes));
       }

       socketChannel.close();
   }

堆外内存

原理直接使用堆外内存,少了一次拷贝

缺点:堆外内存开启耗时,此内存不受JVM控制,如垃圾回收等

实践案例:Netty

耗时:26ms

public static void outSide() throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));

        long start = System.currentTimeMillis();
        Path path = Paths.get("C:\\Users\\Administrator\\Desktop\\222.txt");
        FileChannel open = FileChannel.open(path, StandardOpenOption.READ);
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect((int) open.size());
        open.read(byteBuffer);
        byteBuffer.flip();
        socketChannel.write(byteBuffer);
        socketChannel.shutdownOutput();
        System.out.println("耗时:" + (System.currentTimeMillis() - start));

        ByteBuffer allocate = ByteBuffer.allocate(1024);
        int read = socketChannel.read(allocate);
        if (read > 0) {
            allocate.flip();
            byte[] bytes = new byte[allocate.remaining()];
            allocate.get(bytes);
            System.out.println("服务端发来消息:" + new String(bytes));
        }

        socketChannel.close();
    }

总结

耗时统计不完全准确,都是多次取平均,具体使用哪种需要看场景来

Last Updated: