零拷貝


零拷貝主要是優化內核緩沖區和用戶緩存區的之間拷貝次數

怎么出現一步一步出現零拷貝的呢,下面跟大家講一下。

下圖是當用戶發出讀寫請求到操作系統進行交互的簡單流程圖
image

傳統模式

從上圖描述,把數據從內核緩沖區拷貝到用戶緩沖區只是一次讀操作,但是在網絡編程中,該操作需要4次拷貝,4次上下文切換,因此性能低。
image

零拷貝模式(主要有mmap和sendFlie)

先了解一個概念:
DMA拷貝(direct memory access):直接內存拷貝,不使用CPU

mmap優化

mmap通過內存映射,將內存緩沖區中的數據映射到用戶緩沖區中,這樣用戶空間可以共享內核空間的數據。在進行網絡傳輸時,就可以減少內核空間到用戶空間的CPU拷貝次數。但是還需要將內核緩沖區中的數據通過CPU拷貝到socket緩沖區中。該操作有3次拷貝,4次上下文切換
image

sendFile優化(有兩個版本)

1、在Linux2.1版本中使用sendFile系統調用函數時,把用戶緩沖區去掉了,直接將內核緩沖區通過CPU拷貝到socket緩沖區中(由於把用戶緩沖區去掉了,因此就減少了兩次上下文切換)。該操作3次拷貝,2次上下文切換

2、在Linux在2.4版本中真正實現了零拷貝,對2.1版本進行了修改,避免了將內核緩沖區中的數據通過CPU拷貝到socket緩沖區的操作,而是直接將內核緩沖區中的數據通過DMA拷貝到網卡中。該操作有2次拷貝,2次上下文切換
image

1、與傳統模式相比,零拷貝實現了更少的數據復制、更少的上下文切換、更少的CPU緩存偽共享以及CPU校驗和計算。
2、sendFile可以利用DMA方式來減少CPU拷貝;mmap不可以利用DMA的方式來減少CPU拷貝,而是必須從內核緩沖區將數據通過CPU拷貝到socket緩沖區。

在NIO中,使用fileChannel.transferTo就可以實現零拷貝

  • 在linux一次調用transferTo方法就可以完成傳輸
  • 在windows每次調用transferTo只能發送8MB,就需要分段傳輸文件

案例如下
NewIOServer.java服務端

服務端代碼
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

//服務器
public class NewIOServer {

    public static void main(String[] args) throws Exception {
        InetSocketAddress address = new InetSocketAddress(7001);
        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 作廢
            }
        }
    }
}

NewIOClient.java客戶端

客戶端代碼
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

public class NewIOClient {

    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost", 7001));
        String filename = "protoc-3.6.1-win32.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();
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM