NIO的零拷貝

java中零拷貝有2種(零拷貝是指沒有CPU拷貝)

1,mmap(內存映射)

2,sendfile

傳統IO數據讀寫;

File file = new File("test.txt");

RandomAccessFile raf = new RandomAccessFile(file, "rw");

byte[] arr = new byte[(int) file.length()]; raf.read(arr);

Socket socket = new ServerSocket(8080).accept();

socket.getOutputStream().write(arr);

過程如下;

傳統IO發現copy了4次,cpu狀態切換三次,

所以要優化;

MMAP優化:

mmap 通過內存映射,將文件映射到內核緩衝區,同時,用戶空間可以共享內核空間的數據。這樣,在進行網絡傳輸時,就可以減少內核空間到用戶控件的拷貝次數。如下圖

 

此時copy 3次,狀態也是3次,

 

sendFile 優化

Linux 2.1 版本 提供了 sendFile 函數,其基本原理如下:數據根本不經過用戶態,直接從內核緩衝區進入到 Socket Buffer,同時,由於和用戶態完全無關,就減少了一次上下文切換 示意圖和小結 提示:零拷貝從操作系統角度,是沒有cpu 拷貝

此時是3次拷貝,切換變成了2次;

Linux 在 2.4 版本中,做了一些修改,避免了從內核緩衝區拷貝到 Socket buffer 的操作,直接拷貝到協議棧,從而再一次減少了數據拷貝。具體如下圖和小結: 這裏其實有 一次cpu 拷貝 kernel buffer -> socket buffer 但是,拷貝的信息很少,比如 lenght , offset , 消耗低,可以忽略

上圖中socketbuffer變成灰色,kernel buffer可以通過DMA copy進入 protocol engine;

我們說零拷貝,是從操作系統的角度來說的。因爲內核緩衝區之間,沒有數據是重複的(只有 kernel buffer 有一份數據)。 零拷貝不僅僅帶來更少的數據複製,還能帶來其他的性能優勢,例如更少的上下文切換,更少的 CPU 緩存僞共享以及無 CPU 校驗和計

 

 mmap 和 sendFile 的區別 mmap 適合小數據量讀寫,sendFile 適合大文件傳輸。 mmap 需要 4 次上下文切換,3 次數據拷貝;sendFile 需要 3 次上下文切換,最少 2 次數據拷貝。 sendFile 可以利用 DMA 方式,減少 CPU 拷貝,mmap 則不能(必須從內核拷貝到 Socket 緩衝區)。

例子:

比如下面的零拷貝

public class NewIOClient {
    public static void main(String[] args) throws Exception {

        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost", 6666));
        String filename = "pro*****.zip";

        //得到一個文件channel
        FileChannel fileChannel = new FileInputStream(filename).getChannel();

        //準備發送
        long startTime = System.currentTimeMillis();

        //在linux下一個transferTo 方法就可以完成傳輸
        //在windows 下 一次調用 transferTo 只能發送8m , 就需要分段傳輸文件, 而且要主要
        //傳輸時的位置 =》 
        //transferTo 底層使用到零拷貝
        long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);

        System.out.println("發送的總的字節數 =" + transferCount + " 耗時:" + (System.currentTimeMillis() - startTime));

        //關閉
        fileChannel.close();

    }
}

//服務器
public class NewIOServer {
    public static void main(String[] args) throws Exception {

        InetSocketAddress address = new InetSocketAddress(6666);

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        ServerSocket serverSocket = serverSocketChannel.socket();

        serverSocket.bind(address);

        //創建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(4096);

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

            int readcount = 0;
            while (-1 != readcount) {
                try {

                    readcount = socketChannel.read(byteBuffer);

                }catch (Exception ex) {
                   // ex.printStackTrace();
                    break;
                }
                //
                byteBuffer.rewind(); //倒帶 position = 0 mark 作廢
            }
        }
    }
}

以及老的拷貝方式:

public class OldIOClient {

    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost", 6666);

        String fileName = "proto***.zip";
        InputStream 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();
    }
}

//java IO 的服務器
public class OldIOServer {

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

        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 (-1 == readCount) {
                        break;
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
}
old用時:

文件越大,差異體現的更明顯

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章