国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Netty 之 Zero-copy 的實現(下)

endiat / 1749人閱讀

摘要:系統調用返回,產生了第四次上下文切換。現在這個方法不僅減少了上下文切換,而且消除了參與的數據拷貝。

上一篇說到了 CompositeByteBuf ,這一篇接著上篇的講下去。

FileRegion

讓我們先看一個Netty官方的example

// netty-netty-4.1.16.Finalexamplesrcmainjavaio
ettyexamplefileFileServerHandler.java
public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
    RandomAccessFile raf = null;
    long length = -1;
    try {
        raf = new RandomAccessFile(msg, "r");
        length = raf.length();
    } catch (Exception e) {
        ctx.writeAndFlush("ERR: " + e.getClass().getSimpleName() + ": " + e.getMessage() + "
");
        return;
    } finally {
        if (length < 0 && raf != null) {
            raf.close();
        }
    }

    ctx.write("OK: " + raf.length() + "
");
    if (ctx.pipeline().get(SslHandler.class) == null) {
        // SSL not enabled - can use zero-copy file transfer.
        ctx.write(new DefaultFileRegion(raf.getChannel(), 0, length));
    } else {
        // SSL enabled - cannot use zero-copy file transfer.
        ctx.write(new ChunkedFile(raf));
    }
    ctx.writeAndFlush("
");
}

可以看到在沒開啟SSL的情況下handler是通過 DefaultFileRegion 類傳輸文件的,而 DefaultFileRegionFileRegion 接口的一個實現, FileRegion 的注釋是這么寫的:

A region of a file that is sent via a Channel which supports zero-copy file transfer.

FileRegion 內部封裝了 Java NIO 的 FileChannel.transferTo() 方法,要了解 FileRegionZero-copy 的原理,我們得先了解 transferTo() 方法。

讓我們看一段傳輸文件的一般寫法吧。

File.read(file, buf, len);
Socket.send(socket, buf, len);

盡管上面的代碼看起來很簡單,但在內部實際包含了4次用戶態-內核態上下文切換,和4次數據拷貝。

其中步驟有:

read() 調用導致了一次用戶態到內核態的上下文切換,在內部,一個 sys_read() (或等價函數)被執行來從文件中讀取數據。第一次拷貝是由 DMA 引擎將數據從磁盤文件存儲到內核地址空間緩沖區。

被請求長度的數據從內核的讀緩沖區拷貝到用戶緩沖區,并且 read() 調用返回。這個返回導致又一次從內核態到用戶態的上下文切換。現在數據是存儲在用戶地址空間緩沖區。

send() 調用引起了一次從用戶態到內核態的上下文切換。第三次拷貝又一次將數據放進內核地址空間緩沖區,盡管這一次是放進另一個不同的緩沖區,和目標socket聯系在一起。

send() 系統調用返回,產生了第四次上下文切換。第四次拷貝由 DMA 引擎獨立異步地將數據從內核緩沖區傳遞給協議引擎。

看到這里可能有些讀者會問,read() 函數為什么不直接將數據拷貝到用戶地址空間的緩沖區,而要經內核地址空間的緩沖區轉一次手,這不是白白多了一次拷貝操作嗎?

對IO函數有了解的童鞋肯定知道,在IO函數的背后有一個緩沖區 buffer ,我們平常的讀和寫操作并不是直接和底層硬件設備打交道,而是通過一塊叫緩沖區的內存區域緩存數據來間接讀寫。我們知道,和CPU、高速緩存、內存比,磁盤、網卡這些設備屬于慢速設備,交換一次數據要花很多時間,同時會消耗總線傳輸帶寬,所以我們要盡量降低和這些設備打交道的頻率,而使用緩沖區中轉數據就是為了這個目的。

引用參考文獻[2]中的話:

Using the intermediate buffer on the read side allows the kernel buffer to act as a "readahead cache" when the application hasn"t asked for as much data as the kernel buffer holds. This significantly improves performance when the requested data amount is less than the kernel buffer size. The intermediate buffer on the write side allows the write to complete asynchronously.

大意是說,在讀一側的中間緩沖區可以作為預讀緩存顯著提高當請求數據大小小于內核緩沖區大小時的讀性能,在寫一側的中間緩沖區可以允許寫操作異步完成。

不過,當讀請求數據的大小大于內核緩沖區時這個策略本身會變成一個性能瓶頸,數據在到達應用程序前會在磁盤、內核緩沖區、用戶緩沖區之間反復多次拷貝。

讓我們重新思考下上面的過程,會發現第二次和第三次的拷貝其實是不必要的,我們為什么不直接從讀緩沖區將數據傳輸到socket緩沖區呢?實際上這就是 transferTo() 所做的。

public void transferTo(long position, long count, WritableByteChannel target);

transferTo() 方法將數據從一個文件channel傳輸到一個可寫channel。在內部它依賴于操作系統對 Zero-copy 的支持,在UNIX/Linux系統上, transferTo() 實際會調用 sendfile() 這個系統函數,將數據從一個文件描述符傳輸到另一個。

#include 
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

可以看到我們將上下文切換已經從4次減少到2次,同時把數據拷貝從4次減少到3次(只有1次 CPU 參與,另外2次 DMA 引擎完成),那么我們可不可以把這唯一一次CPU參與的數據拷貝也省掉呢?

如果網卡支持 gather operations 內核就可以進一步減少數據拷貝。在 Linux kernels 2.4 及更新的版本,socket 描述符已經為適應這個需求做了變化。現在這個方法不僅減少了上下文切換,而且消除了CPU參與的數據拷貝。API接口是一樣的,但是實質已經發生了變化:

transferTo() 方法引起 DMA 引擎將文件內容拷貝到內核緩沖區。

沒有數據從內核緩沖區拷貝到socket緩沖區,只有攜帶位置和長度信息的描述符被追加到socket緩沖區上, DMA 引擎直接將內核緩沖區的數據傳遞到協議引擎,全程無需CPU拷貝數據。

到這里大家對 transferTo() 實現 Zero-copy 的原理應該很清楚了吧, FileRegion 是對 transferTo() 的一個封裝,所以也是一樣的。

DirectByteBuffer

DirectByteBuffer 是 Java NIO 用于實現堆外內存的一個很重要的類,而 NettyDirectByteBuffer 作為PooledDirectByteBufUnpooledDirectByteBuf 的內部數據容器(區別于 HeapByteBuf 直接用 byte[] 作為數據容器),以使用和操縱堆外內存。要了解 DirectByteBuffer 怎么實現 Zero-copy,我們要先了解 DirectByteBuffer 這個類和堆外內存。

DirectByteBuffer 類本身還是位于Java內存模型的堆中,堆內存是JVM可以直接管控、操縱的內存,而 DirectByteBuffer 中的 unsafe.allocateMemory(size) 是一個native方法,這個方法分配的是堆外內存,通過 C 的 malloc 來進行分配的。分配的內存是在系統本地的內存,并不在Java的內存中,也不屬于JVM管控范圍,所以在 DirectByteBuffer 一定會存在某種方式操縱堆外內存。

DirectByteBuffer 的父類 Buffer 中有個 address 屬性:

// Used only by direct buffers
// NOTE: hoisted here for speed in JNI GetDirectBufferAddress
long address;

address 只會被直接緩存給使用到。之所以將 address 屬性升級放在 Buffer 中,是為了在JNI調用 GetDirectBufferAddress 時提高效率。

address 表示分配的堆外內存的地址,JNI對這個堆外內存的操作都是通過這個 address 實現的。

在回答為什么堆外內存可以實現 Zero-copy 前,我們先要明確一個結論,那就是 操作系統不能直接訪問Java堆的內存區域

JNI方法訪問的內存區域是一個已經確定的內存區域,如果該內存地址指向的是一個Java堆內存的話,在操作系統正在訪問這個內存地址時,JVM在這個時候進行了GC操作,GC經常會進行先標記再壓縮的操作,即將可回收的空間做標記,然后清空標記位置的內存,然后會進行一個壓縮,壓縮會涉及到對象的移動,以騰出一塊更加完整、連續的內存空間,以容納更大的新對象,但是這個移動的過程會使JNI調用的數據錯亂。

為了解決上述的問題,一般會做一個堆內存與堆外內存之間數據拷貝的操作:比如我們要完成一個從文件中讀數據到堆內存的操作,即 FileChannelImpl.read(HeapByteBuffer) ,這里實際上File I/O會將數據讀到堆外內存中,然后堆外內存再將數據拷貝到堆內存,這樣我們就讀到了文件中的內容。

static int read(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
    if (var1.isReadOnly()) {
        throw new IllegalArgumentException("Read-only buffer");
    } else if (var1 instanceof DirectBuffer) {
        return readIntoNativeBuffer(var0, var1, var2, var4);
    } else {
        // 分配臨時的堆外內存
        ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining());

        int var7;
        try {
            // File I/O 操作會將數據讀入到堆外內存中
            int var6 = readIntoNativeBuffer(var0, var5, var2, var4);
            var5.flip();
            if (var6 > 0) {
                // 將堆外內存的數據拷貝到堆外內存中
                var1.put(var5);
            }

            var7 = var6;
        } finally {
            // 里面會調用DirectBuffer.cleaner().clean()來釋放臨時的堆外內存
            Util.offerFirstTemporaryDirectBuffer(var5);
        }

        return var7;
    }
}

而寫操作則反之,我們會將堆內存的數據先寫到堆外內存,然后操作系統會將堆外內存的數據寫入到堆內存。

如果我們直接使用堆外內存,即直接在堆外分配一塊內存來存儲數據,這樣就可以避免堆內存和堆外內存之間的數據拷貝,進行I/O操作時直接將堆外內存地址傳給JNI的I/O函數就好了。

這里引用一段 stackoverflow 里關于 ByteBuffer.allocate() vs. ByteBuffer.allocateDirect() 的討論:

Operating systems perform I/O operations on memory areas. These memory areas, as far as the operating system is concerned, are contiguous sequences of bytes. It"s no surprise then that only byte buffers are eligible to participate in I/O operations. Also recall that the operating system will directly access the address space of the process, in this case the JVM process, to transfer the data. This means that memory areas that are targets of I/O perations must be contiguous sequences of bytes. In the JVM, an array of bytes may not be stored contiguously in memory, or the Garbage Collector could move it at any time. Arrays are objects in Java, and the way data is stored inside that object could vary from one JVM implementation to another.

這也是堆外內存 DirectByteBuffer 被引進的原因。

但是同時,創建和銷毀一塊堆外內存的花銷要比堆內存昂貴得多,這是因為堆外內存的創建和銷毀要通過系統相關的 native 方法,而不是在 Java 堆上直接由 JVM 操控。為了更有效地重用堆外內存,Netty 引入了內存池機制手動管理內存,這是一個 Java 版的 Jemalloc,后面有機會再寫篇文章專門介紹這個,因為我現在也不是很懂(先挖個坑)。

總結

到這里關于 Netty 實現 Zero-copy 的4種機制,切片共用,組合緩沖區,操作系統層的零拷貝以及堆外內存已經介紹完了,因為本人也是最近剛開始學習 Netty 框架,對很多知識點掌握得還不是很通透,如果文章寫得有什么不妥的地方還請大家不吝賜教。

參考

[1] 對于 Netty ByteBuf 的零拷貝(Zero Copy) 的理解
[2] Efficient data transfer through zero copy
[3] 堆外內存 之 DirectByteBuffer 詳解

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67967.html

相關文章

  • Netty Zero-copy 實現(上)

    摘要:維基百科中對的解釋是零拷貝技術是指計算機執行操作時,不需要先將數據從某處內存復制到另一個特定區域。維基百科里提到的零拷貝是在硬件和操作系統層面的,而本文主要介紹的是在應用層面的優化。 維基百科中對 Zero-copy 的解釋是 零拷貝技術是指計算機執行操作時,CPU不需要先將數據從某處內存復制到另一個特定區域。這種技術通常用于通過網絡傳輸文件時節省CPU周期和內存帶寬。 維基百科里提到...

    sf_wangchong 評論0 收藏0
  • 對于 Netty ByteBuf 零拷貝(Zero Copy) 理解

    摘要:根據對的定義即所謂的就是在操作數據時不需要將數據從一個內存區域拷貝到另一個內存區域因為少了一次內存的拷貝因此的效率就得到的提升在層面上的通常指避免在用戶態與內核態之間來回拷貝數據例如提供的系統調用它可以將一段用戶空間內存映射到內 根據 Wiki 對 Zero-copy 的定義: Zero-copy describes computer operations in which the C...

    ConardLi 評論0 收藏0
  • Netty源碼解析

    摘要:一旦某個事件觸發,相應的則會被調用,并進行處理。事實上,內部的連接處理協議編解碼超時等機制,都是通過完成的。開啟源碼之門理解了的事件驅動機制,我們現在可以來研究的各個模塊了。 Netty是什么 大概用Netty的,無論新手還是老手,都知道它是一個網絡通訊框架。所謂框架,基本上都是一個作用:基于底層API,提供更便捷的編程模型。那么通訊框架到底做了什么事情呢?回答這個問題并不太容易,我們...

    _Suqin 評論0 收藏0
  • Netty 源碼分析 三 我就是大名鼎鼎 EventLoop(一)

    摘要:目錄源碼之下無秘密做最好的源碼分析教程源碼分析之番外篇的前生今世的前生今世之一簡介的前生今世之二小結的前生今世之三詳解的前生今世之四詳解源碼分析之零磨刀不誤砍柴工源碼分析環境搭建源碼分析之一揭開神秘的紅蓋頭源碼分析之一揭開神秘的紅蓋頭客戶端 目錄 源碼之下無秘密 ── 做最好的 Netty 源碼分析教程 Netty 源碼分析之 番外篇 Java NIO 的前生今世 Java NI...

    livem 評論0 收藏0

發表評論

0條評論

endiat

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<