零拷貝機制(Zero-Copy)是在操作數據時不需要將數據從一塊內存區域復制到另一塊內存區域的技術,這樣就避免了內存的拷貝,使得可以提高CPU的。零拷貝機制是一種操作數據的優化方案,通過避免數據在內存中拷貝達到的提高CPU性能的方案。
一、操作系統的零拷貝機制
操作系統的存儲空間包含硬盤和內存,而內存又分成用戶空間和內核空間。以從文件服務器下載文件為例,服務器需要將硬盤中的數據通過網絡通信發送給客戶端,大致流程如下:
第一步:操作系統通過DMA傳輸將硬盤中的數據復制到內核緩沖區
第二步:操作系統執行read方法將內核緩沖區的數據復制到用戶空間
第三步:操作系統執行write方法將用戶空間的數據復制到內核socket緩沖區
第四步:操作系統通過DMA傳輸將內核socket緩沖區數據復制給網卡發送數據
流程如下圖示:
整個流程中:DMA拷貝2次、CPU拷貝2次、用戶空間和內核空間切換4次
整個流程從內核空間和硬件之間數據拷貝是DMA復制傳輸,內核空間和用戶空間之間數據拷貝是通過CPU復制.另外CPU除了需要參與拷貝任務,還需要多次從內核空間和用戶空間之間來回切換,無疑都額外增加了很多的CPU工作負擔。
所以操作系統為了減少CPU拷貝數據帶來的性能消耗,提供了幾種解決方案來減少CPU拷貝次數
1.1、使用mmap函數
mmap函數的作用相當於是內存共享,將內核空間的內存區域和用戶空間共享,這樣就避免了將內核空間的數據拷貝到用戶空間的步驟,通過mmap函數發送數據時上述的步驟如下:
第一步:操作系統通過DMA傳輸將硬盤中的數據復制到內核緩沖區,執行了mmap函數之后,拷貝到內核緩沖區的數據會和用戶空間進行共享,所以不需要進行拷貝
第二步:CPU將內核緩沖區的數據拷貝到內核空間socket緩沖區
第三步:操作系統通過DMA傳輸將內核socket緩沖區數據拷貝給網卡發送數據
流程如下圖示:
整個流程中:DMA拷貝2次、CPU拷貝1次、用戶空間和內核空間切換4次
可以發現此種方案避免了內核空間和用戶空間之間數據的拷貝工作,但是在內核空間內部還是會有一次數據拷貝過程,而且CPU還是會有從內核空間和用戶空間的切換過程
1.2、使用sendfile函數
senfile函數的作用是將一個文件描述符的內容發送給另一個文件描述符。而用戶空間是不需要關心文件描述符的,所以整個的拷貝過程只會在內核空間操作,相當於減少了內核空間和用戶空間之間數據的拷貝過程,而且還避免了CPU在內核空間和用戶空間之間的來回切換過程。整體流程如下:
第一步:通過DMA傳輸將硬盤中的數據復制到內核頁緩沖區
第二步:通過sendfile函數將頁緩沖區的數據通過CPU拷貝給socket緩沖區
第三步:網卡通過DMA傳輸將socket緩沖區的數據拷貝走並發送數據
流程如下圖示:
整個過程中:DMA拷貝2次、CPU拷貝1次、內核空間和用戶空間切換0次
可以看出通過sendfile函數時只會有一次CPU拷貝過程,而且全程都是在內核空間實現的,所以整個過程都不會使得CPU在內核空間和用戶空間進行來回切換的操作,性能相比於mmap而言要更好
另外如果硬件支持的化,sendfile函數還可以直接將文件描述符和數據長度發送給socket緩沖區,然后直接通過DMA傳輸將頁緩沖區的數據拷貝給網卡進行發送即可,這樣就避免了CPU在內核空間內的拷貝過程,流程如下:
第一步:通過DMA傳輸將硬盤中的數據復制到內核頁緩沖區
第二步:通過sendfile函數將頁緩沖區數據的文件描述符和數據長度發送給socket緩沖區
第三步:網卡通過DMA傳輸根據文件描述符和文件長度直接從頁緩沖區拷貝數據
如下圖示:
整個過程中:DMA拷貝2次、CPU拷貝0次、內核空間和用戶空間切換0次
所以整個過程都是沒有CPU拷貝的過程的,實現了真正的CPU零拷貝機制
1.3、使用slice函數
splice函數的作用是將兩個文件描述符之間建立一個管道,然后將文件描述符的引用傳遞過去,這樣在使用到數據的時候就可以直接通過引用指針訪問到具體數據。過程如下:
第一步:通過DMA傳輸將文件復制到內核頁緩沖區
第二步:通過splice函數在頁緩沖區和socket緩沖區之間建立管道,並將文件描述符的引用指針發送給socket緩沖區
第三步:網卡通過DMA傳輸根據文件描述符的指針直接訪問數據
如下圖示:
整個過程中: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,避免了內存拷貝