什么是零拷貝
零拷貝(Zero-Copy)技術是指電腦執行操作時,CPU不需要參與數據的搬運復制。這種技術通常用于網絡傳輸文件時,節省CPU周期和內存帶寬。
傳統的IO流程
早期的I/O原始過程是這樣的:
CPU發出指令給磁盤控制器,然后返回;
磁盤控制器收到指令后,將數據復制到磁盤的內部緩沖區,隨后對CPU發起IO中斷信號
CPU收到中斷信號后,將緩沖區的讀到寄存器中,再將寄存器中的數據寫入的內存,寫入到內存期間CPU是無法執行其他任務
執行過程如圖所示:
整個數據搬運到內存的過程中都需要CPU參與計算。如果用到千兆網卡或者磁盤傳輸大量數據的時候,CPU一直處于搬運復制數據的過程中,將會對系統的負載和吞吐量產生比較大的影響。
于是發明了DMA(Direct Memory Acess)技術,也就是直接內存訪問。簡單理解就是,在磁盤和內存進行數據搬運時,這些工作會由DMA控制器進行,而不是CPU,這樣可以減輕CPU的負載。執行過程如下圖:
可以看到,整個數據從磁盤到內存傳輸的過程中,CPU不再參與搬運,全都是DMA控制器完成。早期DMA只存在于主板上,如今基本上每個I/0設備都有自己的DMA控制器。
如果服務端需要有文件傳輸的功能,簡單的方式是:調用系統read()函數 將磁盤文件讀入內存,然后通過調用系統write()函數將內存數據寫給網絡協議棧發送給客戶端。如下圖:
首先可以看到,讀磁盤文件寫入到網卡,一共經歷了4次的用戶態和內核態的切換。原因是:用戶線程調用了系統函數一次read()和一次write(),每次系統調用都需要先從用戶態切換到內核態,等內核態完成任務后,再從內核態切換回用戶態。
其次,發送了4次數據拷貝,兩次拷貝是由DMA完成的,兩次拷貝是CPU完成的。
由此我們可以分析出,搬運一份數據存在冗余的用戶態和內核態的切換以及多余的拷貝。所以想要提高文件傳輸性能,需要減少用戶態和內核態的切換和拷貝次數。
如何實現零拷貝
可以通過調用sendfile()函數替代前面的read()和write()系統調用,這樣可以減少一次系統調用,也就減少了兩次次用戶態和內核態之間的切換開銷。其次,該函數可以直接把內核緩沖區里面的數據拷貝的socket緩沖區,這樣就只有2次上下文切換,和3次數據拷貝。如下圖所示:
但是這個還不是真正的零拷貝技術,從內核2.4版本開始,如果網卡支持SG-DMA(The Scatter-Gather Direct Memory Access),可以進一步減少CPU把內核緩沖區里面的數據拷貝到socket緩沖區的過程。可以通過以下命令查看網卡是否支持scatter-gather特性:
于是,從內核2.4版本開始,對于網卡支持SG-DMA技術的情況下,sendfile() 系統調用過程可以實現CPU的零拷貝,整個過程如下圖所示:
這就是所謂的零拷貝(Zero-copy)技術,因為我們沒有在內存層面去拷貝數據,也就是說全程沒有通過cpu來搬運數據,所有的數據都是由DMA來進行傳輸的。
零拷貝技術的文件傳輸方式相比傳統文件傳輸的方式,減少了兩次用戶態和內核態的切換和數據拷貝次數。所以總體上看,零拷貝技術可以把文件的傳輸性能提高至少一倍以上。
零拷貝實踐總結
Java提供了NIO庫中的transferTo方法,go語言中的syscall包中的Sendfile都提供了直接操作底層零拷貝技術的能力。比較火熱的kafka開源項目,也利用了零拷貝技術,大幅提升I/0吞吐量,這也是kafka能夠處理海量數據的原因之一。
參考一些資料上的性能測試數據,同一個硬件條件下,傳統文件傳輸和零拷貝文件傳輸性能差異,可以看下圖的測試數據圖,使用零拷貝能縮短65%的時間。