成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Netty 之 Zero-copy 的實(shí)現(xiàn)(下)

endiat / 1764人閱讀

摘要:系統(tǒng)調(diào)用返回,產(chǎn)生了第四次上下文切換。現(xiàn)在這個(gè)方法不僅減少了上下文切換,而且消除了參與的數(shù)據(jù)拷貝。

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

FileRegion

讓我們先看一個(gè)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("
");
}

可以看到在沒(méi)開(kāi)啟SSL的情況下handler是通過(guò) DefaultFileRegion 類傳輸文件的,而 DefaultFileRegionFileRegion 接口的一個(gè)實(shí)現(xiàn), FileRegion 的注釋是這么寫的:

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

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

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

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

盡管上面的代碼看起來(lái)很簡(jiǎn)單,但在內(nèi)部實(shí)際包含了4次用戶態(tài)-內(nèi)核態(tài)上下文切換,和4次數(shù)據(jù)拷貝。

其中步驟有:

read() 調(diào)用導(dǎo)致了一次用戶態(tài)到內(nèi)核態(tài)的上下文切換,在內(nèi)部,一個(gè) sys_read() (或等價(jià)函數(shù))被執(zhí)行來(lái)從文件中讀取數(shù)據(jù)。第一次拷貝是由 DMA 引擎將數(shù)據(jù)從磁盤文件存儲(chǔ)到內(nèi)核地址空間緩沖區(qū)。

被請(qǐng)求長(zhǎng)度的數(shù)據(jù)從內(nèi)核的讀緩沖區(qū)拷貝到用戶緩沖區(qū),并且 read() 調(diào)用返回。這個(gè)返回導(dǎo)致又一次從內(nèi)核態(tài)到用戶態(tài)的上下文切換。現(xiàn)在數(shù)據(jù)是存儲(chǔ)在用戶地址空間緩沖區(qū)。

send() 調(diào)用引起了一次從用戶態(tài)到內(nèi)核態(tài)的上下文切換。第三次拷貝又一次將數(shù)據(jù)放進(jìn)內(nèi)核地址空間緩沖區(qū),盡管這一次是放進(jìn)另一個(gè)不同的緩沖區(qū),和目標(biāo)socket聯(lián)系在一起。

send() 系統(tǒng)調(diào)用返回,產(chǎn)生了第四次上下文切換。第四次拷貝由 DMA 引擎獨(dú)立異步地將數(shù)據(jù)從內(nèi)核緩沖區(qū)傳遞給協(xié)議引擎。

看到這里可能有些讀者會(huì)問(wèn),read() 函數(shù)為什么不直接將數(shù)據(jù)拷貝到用戶地址空間的緩沖區(qū),而要經(jīng)內(nèi)核地址空間的緩沖區(qū)轉(zhuǎn)一次手,這不是白白多了一次拷貝操作嗎?

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

引用參考文獻(xiàn)[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.

大意是說(shuō),在讀一側(cè)的中間緩沖區(qū)可以作為預(yù)讀緩存顯著提高當(dāng)請(qǐng)求數(shù)據(jù)大小小于內(nèi)核緩沖區(qū)大小時(shí)的讀性能,在寫一側(cè)的中間緩沖區(qū)可以允許寫操作異步完成。

不過(guò),當(dāng)讀請(qǐng)求數(shù)據(jù)的大小大于內(nèi)核緩沖區(qū)時(shí)這個(gè)策略本身會(huì)變成一個(gè)性能瓶頸,數(shù)據(jù)在到達(dá)應(yīng)用程序前會(huì)在磁盤、內(nèi)核緩沖區(qū)、用戶緩沖區(qū)之間反復(fù)多次拷貝。

讓我們重新思考下上面的過(guò)程,會(huì)發(fā)現(xiàn)第二次和第三次的拷貝其實(shí)是不必要的,我們?yōu)槭裁床恢苯訌淖x緩沖區(qū)將數(shù)據(jù)傳輸?shù)絪ocket緩沖區(qū)呢?實(shí)際上這就是 transferTo() 所做的。

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

transferTo() 方法將數(shù)據(jù)從一個(gè)文件channel傳輸?shù)揭粋€(gè)可寫channel。在內(nèi)部它依賴于操作系統(tǒng)對(duì) Zero-copy 的支持,在UNIX/Linux系統(tǒng)上, transferTo() 實(shí)際會(huì)調(diào)用 sendfile() 這個(gè)系統(tǒng)函數(shù),將數(shù)據(jù)從一個(gè)文件描述符傳輸?shù)搅硪粋€(gè)。

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

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

如果網(wǎng)卡支持 gather operations 內(nèi)核就可以進(jìn)一步減少數(shù)據(jù)拷貝。在 Linux kernels 2.4 及更新的版本,socket 描述符已經(jīng)為適應(yīng)這個(gè)需求做了變化?,F(xiàn)在這個(gè)方法不僅減少了上下文切換,而且消除了CPU參與的數(shù)據(jù)拷貝。API接口是一樣的,但是實(shí)質(zhì)已經(jīng)發(fā)生了變化:

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

沒(méi)有數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到socket緩沖區(qū),只有攜帶位置和長(zhǎng)度信息的描述符被追加到socket緩沖區(qū)上, DMA 引擎直接將內(nèi)核緩沖區(qū)的數(shù)據(jù)傳遞到協(xié)議引擎,全程無(wú)需CPU拷貝數(shù)據(jù)。

到這里大家對(duì) transferTo() 實(shí)現(xiàn) Zero-copy 的原理應(yīng)該很清楚了吧, FileRegion 是對(duì) transferTo() 的一個(gè)封裝,所以也是一樣的。

DirectByteBuffer

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

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

DirectByteBuffer 的父類 Buffer 中有個(gè) address 屬性:

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

address 只會(huì)被直接緩存給使用到。之所以將 address 屬性升級(jí)放在 Buffer 中,是為了在JNI調(diào)用 GetDirectBufferAddress 時(shí)提高效率。

address 表示分配的堆外內(nèi)存的地址,JNI對(duì)這個(gè)堆外內(nèi)存的操作都是通過(guò)這個(gè) address 實(shí)現(xiàn)的。

在回答為什么堆外內(nèi)存可以實(shí)現(xiàn) Zero-copy 前,我們先要明確一個(gè)結(jié)論,那就是 操作系統(tǒng)不能直接訪問(wèn)Java堆的內(nèi)存區(qū)域 。

JNI方法訪問(wèn)的內(nèi)存區(qū)域是一個(gè)已經(jīng)確定的內(nèi)存區(qū)域,如果該內(nèi)存地址指向的是一個(gè)Java堆內(nèi)存的話,在操作系統(tǒng)正在訪問(wèn)這個(gè)內(nèi)存地址時(shí),JVM在這個(gè)時(shí)候進(jìn)行了GC操作,GC經(jīng)常會(huì)進(jìn)行先標(biāo)記再壓縮的操作,即將可回收的空間做標(biāo)記,然后清空標(biāo)記位置的內(nèi)存,然后會(huì)進(jìn)行一個(gè)壓縮,壓縮會(huì)涉及到對(duì)象的移動(dòng),以騰出一塊更加完整、連續(xù)的內(nèi)存空間,以容納更大的新對(duì)象,但是這個(gè)移動(dòng)的過(guò)程會(huì)使JNI調(diào)用的數(shù)據(jù)錯(cuò)亂。

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

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 {
        // 分配臨時(shí)的堆外內(nèi)存
        ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining());

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

            var7 = var6;
        } finally {
            // 里面會(huì)調(diào)用DirectBuffer.cleaner().clean()來(lái)釋放臨時(shí)的堆外內(nèi)存
            Util.offerFirstTemporaryDirectBuffer(var5);
        }

        return var7;
    }
}

而寫操作則反之,我們會(huì)將堆內(nèi)存的數(shù)據(jù)先寫到堆外內(nèi)存,然后操作系統(tǒng)會(huì)將堆外內(nèi)存的數(shù)據(jù)寫入到堆內(nèi)存。

如果我們直接使用堆外內(nèi)存,即直接在堆外分配一塊內(nèi)存來(lái)存儲(chǔ)數(shù)據(jù),這樣就可以避免堆內(nèi)存和堆外內(nèi)存之間的數(shù)據(jù)拷貝,進(jìn)行I/O操作時(shí)直接將堆外內(nèi)存地址傳給JNI的I/O函數(shù)就好了。

這里引用一段 stackoverflow 里關(guān)于 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.

這也是堆外內(nèi)存 DirectByteBuffer 被引進(jìn)的原因。

但是同時(shí),創(chuàng)建和銷毀一塊堆外內(nèi)存的花銷要比堆內(nèi)存昂貴得多,這是因?yàn)槎淹鈨?nèi)存的創(chuàng)建和銷毀要通過(guò)系統(tǒng)相關(guān)的 native 方法,而不是在 Java 堆上直接由 JVM 操控。為了更有效地重用堆外內(nèi)存,Netty 引入了內(nèi)存池機(jī)制手動(dòng)管理內(nèi)存,這是一個(gè) Java 版的 Jemalloc,后面有機(jī)會(huì)再寫篇文章專門介紹這個(gè),因?yàn)槲椰F(xiàn)在也不是很懂(先挖個(gè)坑)。

總結(jié)

到這里關(guān)于 Netty 實(shí)現(xiàn) Zero-copy 的4種機(jī)制,切片共用,組合緩沖區(qū),操作系統(tǒng)層的零拷貝以及堆外內(nèi)存已經(jīng)介紹完了,因?yàn)楸救艘彩亲罱鼊傞_(kāi)始學(xué)習(xí) Netty 框架,對(duì)很多知識(shí)點(diǎn)掌握得還不是很通透,如果文章寫得有什么不妥的地方還請(qǐng)大家不吝賜教。

參考

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

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/67967.html

相關(guān)文章

  • Netty Zero-copy 實(shí)現(xiàn)(上)

    摘要:維基百科中對(duì)的解釋是零拷貝技術(shù)是指計(jì)算機(jī)執(zhí)行操作時(shí),不需要先將數(shù)據(jù)從某處內(nèi)存復(fù)制到另一個(gè)特定區(qū)域。維基百科里提到的零拷貝是在硬件和操作系統(tǒng)層面的,而本文主要介紹的是在應(yīng)用層面的優(yōu)化。 維基百科中對(duì) Zero-copy 的解釋是 零拷貝技術(shù)是指計(jì)算機(jī)執(zhí)行操作時(shí),CPU不需要先將數(shù)據(jù)從某處內(nèi)存復(fù)制到另一個(gè)特定區(qū)域。這種技術(shù)通常用于通過(guò)網(wǎng)絡(luò)傳輸文件時(shí)節(jié)省CPU周期和內(nèi)存帶寬。 維基百科里提到...

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

    摘要:根據(jù)對(duì)的定義即所謂的就是在操作數(shù)據(jù)時(shí)不需要將數(shù)據(jù)從一個(gè)內(nèi)存區(qū)域拷貝到另一個(gè)內(nèi)存區(qū)域因?yàn)樯倭艘淮蝺?nèi)存的拷貝因此的效率就得到的提升在層面上的通常指避免在用戶態(tài)與內(nèi)核態(tài)之間來(lái)回拷貝數(shù)據(jù)例如提供的系統(tǒng)調(diào)用它可以將一段用戶空間內(nèi)存映射到內(nèi) 根據(jù) Wiki 對(duì) Zero-copy 的定義: Zero-copy describes computer operations in which the C...

    ConardLi 評(píng)論0 收藏0
  • Netty源碼解析

    摘要:一旦某個(gè)事件觸發(fā),相應(yīng)的則會(huì)被調(diào)用,并進(jìn)行處理。事實(shí)上,內(nèi)部的連接處理協(xié)議編解碼超時(shí)等機(jī)制,都是通過(guò)完成的。開(kāi)啟源碼之門理解了的事件驅(qū)動(dòng)機(jī)制,我們現(xiàn)在可以來(lái)研究的各個(gè)模塊了。 Netty是什么 大概用Netty的,無(wú)論新手還是老手,都知道它是一個(gè)網(wǎng)絡(luò)通訊框架。所謂框架,基本上都是一個(gè)作用:基于底層API,提供更便捷的編程模型。那么通訊框架到底做了什么事情呢?回答這個(gè)問(wèn)題并不太容易,我們...

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

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

    livem 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<