淺析作業系統和Netty中的零複製機制

零複製機制(Zero-Copy)是在操作資料時不需要將資料從一塊記憶體區域複製到另一塊記憶體區域的技術,這樣就避免了記憶體的複製,使得可以提高CPU的。零複製機制是一種操作資料的最佳化方案,透過避免資料在記憶體中複製達到的提高CPU效能的方案。

一、作業系統的零複製機制

作業系統的儲存空間包含硬碟和記憶體,而記憶體又分成使用者空間和核心空間。以從檔案伺服器下載檔案為例,伺服器需要將硬碟中的資料透過網路通訊傳送給客戶端,大致流程如下:

第一步:作業系統透過DMA傳輸將硬碟中的資料複製到核心緩衝區

第二步:作業系統執行read方法將核心緩衝區的資料複製到使用者空間

第三步:作業系統執行write方法將使用者空間的資料複製到核心socket緩衝區

第四步:作業系統透過DMA傳輸將核心socket緩衝區資料複製給網絡卡傳送資料

流程如下圖示:

淺析作業系統和Netty中的零複製機制

整個流程中:DMA複製2次、CPU複製2次、使用者空間和核心空間切換4次

整個流程從核心空間和硬體之間資料複製是DMA複製傳輸,核心空間和使用者空間之間資料複製是透過CPU複製。另外CPU除了需要參與複製任務,還需要多次從核心空間和使用者空間之間來回切換,無疑都額外增加了很多的CPU工作負擔。

所以作業系統為了減少CPU複製資料帶來的效能消耗,提供了幾種解決方案來減少CPU複製次數

1。1、使用mmap函式

mmap函式的作用相當於是記憶體共享,將核心空間的記憶體區域和使用者空間共享,這樣就避免了將核心空間的資料複製到使用者空間的步驟,透過mmap函式傳送資料時上述的步驟如下:

第一步:作業系統透過DMA傳輸將硬碟中的資料複製到核心緩衝區,執行了mmap函式之後,複製到核心緩衝區的資料會和使用者空間進行共享,所以不需要進行複製

第二步:CPU將核心緩衝區的資料複製到核心空間socket緩衝區

第三步:作業系統透過DMA傳輸將核心socket緩衝區資料複製給網絡卡傳送資料

流程如下圖示:

淺析作業系統和Netty中的零複製機制

整個流程中:DMA複製2次、CPU複製1次、使用者空間和核心空間切換4次

可以發現此種方案避免了核心空間和使用者空間之間資料的複製工作,但是在核心空間內部還是會有一次資料複製過程,而且CPU還是會有從核心空間和使用者空間的切換過程

1。2、使用sendfile函式

senfile函式的作用是將一個檔案描述符的內容傳送給另一個檔案描述符。而使用者空間是不需要關心檔案描述符的,所以整個的複製過程只會在核心空間操作,相當於減少了核心空間和使用者空間之間資料的複製過程,而且還避免了CPU在核心空間和使用者空間之間的來回切換過程。整體流程如下:

第一步:透過DMA傳輸將硬碟中的資料複製到核心頁緩衝區

第二步:透過sendfile函式將頁緩衝區的資料透過CPU複製給socket緩衝區

第三步:網絡卡透過DMA傳輸將socket緩衝區的資料複製走併發送資料

流程如下圖示:

淺析作業系統和Netty中的零複製機制

整個過程中:DMA複製2次、CPU複製1次、核心空間和使用者空間切換0次

可以看出透過sendfile函式時只會有一次CPU複製過程,而且全程都是在核心空間實現的,所以整個過程都不會使得CPU在核心空間和使用者空間進行來回切換的操作,效能相比於mmap而言要更好

另外如果硬體支援的化,sendfile函式還可以直接將檔案描述符和資料長度傳送給socket緩衝區,然後直接透過DMA傳輸將頁緩衝區的資料複製給網絡卡進行傳送即可,這樣就避免了CPU在核心空間內的複製過程,流程如下:

第一步:透過DMA傳輸將硬碟中的資料複製到核心頁緩衝區

第二步:透過sendfile函式將頁緩衝區資料的檔案描述符和資料長度傳送給socket緩衝區

第三步:網絡卡透過DMA傳輸根據檔案描述符和檔案長度直接從頁緩衝區複製資料

如下圖示:

淺析作業系統和Netty中的零複製機制

整個過程中:DMA複製2次、CPU複製0次、核心空間和使用者空間切換0次

所以整個過程都是沒有CPU複製的過程的,實現了真正的CPU零複製機制

1。3、使用slice函式

splice函式的作用是將兩個檔案描述符之間建立一個管道,然後將檔案描述符的引用傳遞過去,這樣在使用到資料的時候就可以直接透過引用指標訪問到具體資料。過程如下:

第一步:透過DMA傳輸將檔案複製到核心頁緩衝區

第二步:透過splice函式在頁緩衝區和socket緩衝區之間建立管道,並將檔案描述符的引用指標傳送給socket緩衝區

第三步:網絡卡透過DMA傳輸根據檔案描述符的指標直接訪問資料

如下圖示:

淺析作業系統和Netty中的零複製機制

整個過程中:DMA複製2次、CPU複製0次、核心空間和使用者空間切換0次

可以看出透過slice函式傳輸資料時同樣可以實現CPU的零複製,且不需要CPU在核心空間和使用者空間之間來回切換

總結:實際上作業系統的零複製機制只是針對於CPU的零複製,而核心空間和硬體之間還是會存在資料複製的過程,只不過透過DMA傳輸,而不需要CPU來參與資料的複製過程可以看出透過mmap函式可以減少一次CPU複製,但是還會有一個CPU複製。而使用sendfile和splice函式都已經實現了CPU零複製而實現了資料傳輸過程。

二、Java中的零複製機制

Java的應用程式經常會遇到資料傳輸的場景,在Java NIO包中就提供了零複製機制的實現,主要是透過NIO包中的FileChannel實現FileChannel提供了transferTo和transferFrom方法,都是採用了呼叫底層作業系統的sendfile函式來實現的CPU零複製機制。

kafka伺服器就是採用了FileChannel的transfer方法實現了高效能的IO傳輸操作

Netty中的零複製機制Netty作為NIO的高效能網路通訊框架,同樣也實現了零複製機制,不過和作業系統的零複製機制則不是一個概念

Netty中的零複製機制體現在多個場景:

1、使用直接記憶體,在進行IO資料傳輸時避免了ByteBuf從堆外記憶體複製到堆內記憶體的步驟,而如果使用堆內記憶體分配ByteBuf的化,那麼傳送資料時需要將IO資料從堆內記憶體複製到堆外記憶體才能透過Socket傳送

2、Netty的檔案傳輸使用了FileChannel的transferTo方法,底層使用到sendfile函式來實現了CPU零複製

3、Netty中提供CompositeByteBuf類,用於將多個ByteBuf合併成邏輯上的ByteBuf,避免了將多個ByteBuf複製成一個ByteBuf的過程

4、ByteBuf支援slice方法可以將ByteBuf分解成多個共享記憶體區域的ByteBuf,避免了記憶體複製