零拷貝

簡介

指計算機在執(zhí)行操作的時候,cpu不需要先將數據從某處復制到一個特定地方,節(jié)省cpu的時鐘周期和內存帶寬

常規(guī)流程

image.png

DMA:DIRECT MEMORY ACCESS
這個東西不消耗cpu(從磁盤到內核read緩沖區(qū),從內核socket緩沖區(qū)到網卡都是DMA)

mmap

image.png

實現原理:所有的操作系統(tǒng)都使用的是虛擬內存,取代物理內存,虛擬內存遠大于物理內存,
所有一個以上的虛擬內存可以指向同一個物理內存,這樣DMA就可以填充對內核及用戶進程空間同時可見的緩沖區(qū)
節(jié)省了內核到用戶空間的copy,但是從內核read緩沖區(qū)到內核socket緩沖區(qū)的拷貝沒有節(jié)省

NIO的FileChannel.map底層就是封裝了 linux的 mmap
java實現

public static void main(String[] args) {
        File file = new File("");
        try {
            //打開FileChannel只能讀取
            FileChannel fileChannelIn = new FileInputStream(file).getChannel();
            //打開FileChannel只能寫入
            FileChannel fileChannel1Out = new FileInputStream("").getChannel();
            //讀入數據轉為MappedByteBuffer
            MappedByteBuffer mbb = fileChannelIn.map(FileChannel.MapMode.READ_ONLY,0,file.length());
            //創(chuàng)建解碼器
            Charset charset = Charset.forName("UTF-8");
            //寫入數據
            fileChannel1Out.write(mbb);
            mbb.clear();
            //創(chuàng)建解碼器
            CharsetDecoder decoder = charset.newDecoder();
            // 使用解碼器將byteBuffer轉為CharBuffer
            CharBuffer charBuffer = decoder.decode(mbb);
            System.out.println(charBuffer);
        }catch (Exception e){
            return;
        }
    }

sendFile

適用于:保存了mmap的不需要來回拷貝的優(yōu)點,適用于應用進程不需要對讀取數據做任何處理的場景,類似于一種完全意義上的數據傳輸

這個實現的拷貝,主要做的實現了數據的偏移量offset,數據長度length的拷貝

image.png

不存在用戶緩沖區(qū)(不需要使用)
數據甚至不用從內核緩沖區(qū)拷貝到socket的緩沖區(qū),只需要將內核緩沖區(qū)的拷貝一些offset和length到socket緩沖區(qū)
數量少可以忽略所以認為不存在此次copy

nio中使用的是FileChannel.transferTo,底層封裝的是linux的sendfile這個方法

只有兩次的上下文切換和兩次dma切換

public static void main(String[] args) {
        String files[] = new String[1];
        files[0] = System.getProperty("usr.dir") + "/c:\\aa\\dn.txt";
        catFiles(Channels.newChannel(System.out), files);
    }

    private static void catFiles(WritableByteChannel target, String[] files) {
        try{
            for (int i = 0; i < files.length; i++) {
                FileInputStream fileInputStream = new FileInputStream(files[1]);
                FileChannel channel = fileInputStream.getChannel();
                channel.transferTo(0,channel.size(),target);
                channel.close();
                fileInputStream.close();
            }  
        } catch (Exception e){
            System.out.println(e);
        }
    }

總結

傳統(tǒng)io有4次上下文切換,4次拷貝
磁盤 》 內核read 》用戶 》 內核socket 》網卡(協(xié)議引擎)
mmap:三次拷貝(兩次DMA + 一次內核read緩沖到內核socket緩存)。將磁盤文件映射到內存,支持讀寫內存文件直接反映到磁盤文件上,適合小文件讀取
sendfile:兩次拷貝(兩次DMA),適合大文件傳輸

Netty的零拷貝

1.Netty的接收和發(fā)送ByteBuffer采用的是Direct buffers,使用堆外內存直接進行socket讀取,如果是傳統(tǒng)內存,jvm會將堆內存buffer拷貝一份到直接內存中,再寫入socket,所以傳統(tǒng)堆內存在消息發(fā)送的過程中多了一次緩沖區(qū)的內存拷貝
2.提供了bytebuf對象,整合了bytebuffer,用戶通過bytebuf可以像操作一個buffer那樣對組合的buffer進行操作
做法是實現CompositeByteBuf

image.png

CompositeByteBuf 實際就是個 ByteBuf 的包裝器,它將多個 ByteBuf 組合成一個集合
該類相關定義如下

private static final ByteBuffer EMPTY_NIO_BUFFER = Unpooled.EMPTY_BUFFER.nioBuffer();
private static final Iterator<ByteBuf> EMPTY_ITERATOR = Collections.<ByteBuf>emptyList().iterator(); 
private final ByteBufAllocator alloc; 
private final boolean direct; 
private final List<Component> components; private final int maxNumComponents;
private boolean freed;

3.文件傳輸采用transferTo,可以將文件緩沖區(qū)的數據發(fā)送到目標channel避免通過傳統(tǒng)的write方式導致的內存拷貝問題
這個就是nio里的FileChannel的transferTo
4.wrap
5.slice

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容