netty学习笔记java nio回顾四之零拷贝

netty中有零拷贝的概念,零拷贝从系统层面看就是减少不必要数据拷贝,包括内核空间到用户空间之间的拷贝,还有内核到硬件及socket之间的拷贝。

image.png
read系统调用会把数据从硬件加载到系统内核,再从系统内核copy到用户空间。

image.png
sendfile系统调用,数据直接从内核空间发送到socket,不用再拷贝到用户空间。
下面的Java代码比较一下减少不必要的内存拷贝,效率会提升多少。

public class OldIOServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8899);

        while (true) {
            Socket socket = serverSocket.accept();
            DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());

            try {
                byte[] byteArray = new byte[4096];
                while (true) {
                    int readCount = dataInputStream.read(byteArray, 0, byteArray.length);
                    if (readCount == -1) {
                        break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
public class OldIOClient {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost", 8899);

        String fileName = "/Users/heroz/Downloads/ideaIU-2020.3.dmg";
        FileInputStream inputStream = new FileInputStream(fileName);

        DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());

        byte[] buffer = new byte[4096];
        long readCount;
        long total = 0;

        long startTime = System.currentTimeMillis();
        while ((readCount = inputStream.read(buffer)) >= 0) {
            total += readCount;
            dataOutputStream.write(buffer);
        }
        System.out.println("发送总字节数:" + total + ", 耗时:" + (System.currentTimeMillis() - startTime));

        dataOutputStream.close();
        socket.close();
        inputStream.close();
    }
}
复制代码

这个是客户端发送到服务端接收完所有数据,数据会从内核空间copy到jvm的进程空间,在我的机器上跑耗时大概:发送总字节数:858261174, 耗时:950ms

public class NewIOServer {
    public static void main(String[] args) throws Exception {
        InetSocketAddress address = new InetSocketAddress(8899);

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        ServerSocket socket = serverSocketChannel.socket();
        socket.setReuseAddress(true);
        socket.bind(address);

        ByteBuffer byteBuffer = ByteBuffer.allocate(4096);

        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(true);

            int readCount = 0;

            while (readCount != -1) {
                readCount = socketChannel.read(byteBuffer);
                byteBuffer.rewind();
            }
        }
    }
}

public class NewIOClient {
    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost", 8899));
        socketChannel.configureBlocking(true);

        String fileName = "/Users/heroz/Downloads/ideaIU-2020.3.dmg";

        FileChannel fileChannel = new FileInputStream(fileName).getChannel();

        long startTime = System.currentTimeMillis();

        long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
        System.out.println("发送总字节数:" + transferCount + ", 耗时:" + (System.currentTimeMillis() - startTime));

    }
}
复制代码

channel的transferTo调用,会减少数据的拷贝,在方法注释中有如下说明:
This method is potentially much more efficient than a simple loop that reads from this channel and writes to the target channel. Many operating systems can transfer bytes directly from the filesystem cache to the target channel without actually copying them.
发送同样的数据,这个的耗时:发送总字节数:858261174, 耗时:295ms。
差不多是前面耗时的1/3,效率提升还是很明显的。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享