零拷貝,性能優化必爭之地!

DMA


直接內存訪問(Direct Memory Access)

什麼是 DMA?

在進行數據傳輸的時候,數據搬運的工作全部交給 DMA 控制器,而 CPU 不再參與,可以去幹別的事情。

傳統 I/O

在沒有 DMA 技術前,全程數據拷貝都需要 CPU 來做,嚴重消耗 CPU。

利用 DMA 的 IO

利用 DMA 之後:

DMA 控制器進行數據傳輸的過程:

利用 DMA 的 IO 完整流程圖:

1、CPU 需對 DMA 控制器下發指令,告訴它想讀取多少數據,讀完的數據放在內存;

2、接下來,DMA 控制器會向磁盤控制器發出指令,通知它從磁盤讀數據到其內部的緩衝區中,

3、接着磁盤控制器將緩衝區的數據傳輸到內存;

4、數據拷貝成功之後,磁盤控制器在總線上發出一個確認成功的信號到 DMA 控制器;

5、DMA 控制器收到信號後,DMA 控制器通過中斷通知 CPU 指令完成,CPU 就可以直接取內存裏面現成的數據了;

可以看到,僅僅在傳送開始和結束時需要 CPU 干預,其他任務交由 DMA 處理。

因爲發生了 read+write 兩次系統調用,所以一共發生了 4 次用戶態與內核態的上下文切換

上下文切換的成本並不小,一次切換需要耗時幾十納秒到幾微秒

還發生了 4 次數據拷貝,其中兩次是 CPU 參與的拷貝。

如何優化?

減少「用戶態與內核態的上下文切換」和「數據拷貝」的次數。

1、如何減少「用戶態與內核態的上下文切換」的次數呢?

讀取磁盤數據的時候,之所以要發生上下文切換,是因爲用戶空間沒有權限操作磁盤或網卡,這些操作設備的過程只能交由 OS 內核來完成。所以需要系統調用進行上下文切換,切換到內核態。

所以,減少上下文切換到次數的辦法就是:

減少系統調用的次數

2、如何減少「數據拷貝」的次數?

從內核的讀緩衝區 ----- 用戶的緩衝區裏 ----- socket 的緩衝區裏,這個過程是沒有必要的。

因爲文件傳輸的應用場景中,在用戶空間我們並不會對數據「再加工」,所以數據實際上可以不用搬運到用戶空間,因此用戶的緩衝區是沒有必要存在的。

零拷貝


零拷貝技術實現的方式通常有 2 種:

mmap + write

在前面我們知道,read() 系統調用的過程中會把內核緩衝區的數據拷貝到用戶的緩衝區裏,於是爲了減少這一步開銷,我們可以用 mmap() 替換 read() 系統調用函數。

mmap 系統調用函數會直接把內核緩衝區裏的數據共享到用戶空間,這樣,操作系統內核與用戶空間就不需要再進行任何的數據拷貝操作。

具體過程如下:

性能如何?

mmap 詳解

是什麼?

mmap 是一種實現內存映射文件的方法。

即:將一個文件映射到用戶進程的地址空間,實現文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關係。

實現這樣的映射關係後,進程就可以採用指針的方式讀寫操作這一段內存,而系統會自動回寫髒頁面到對應的文件磁盤上,即完成了對文件的操作,又不必再調用 read,write 等系統調用函數。

相應地,內核空間對這段區域的修改也直接反映到用戶空間,從而可以實現不同用戶進程間的文件共享。

mmap 內存映射的實現過程,總的來說可以分爲三個階段:

1、進程啓動映射過程,並在虛擬地址空間中爲映射創建虛擬映射區域

2、調用 mmap 實現文件的物理地址和進程虛擬地址的一一映射關係

注:前兩個階段僅在於創建虛擬區間並完成地址映射,還沒有將任何文件數據拷貝至主存。真正的文件讀取是當進程發起讀或寫操作時開始。

3、進程發起對這片映射空間的訪問,引發缺頁中斷,實現文件到內核緩衝區的拷貝

mmap 的功能:

1、上面已經分析了,mmap 最大的功能就是減少了數據的拷貝次數

2、提供了進程間共享內存及相互通信的方式。

不管是父子進程還是無親緣關係的進程,都可以將自身用戶空間映射到同一個文件。從而通過各自對映射區域的改動,達到進程間通信和進程間共享的目的。

同時,如果進程 A 和進程 B 都映射了區域 C,當 A 第一次讀取 C 時通過缺頁從磁盤複製文件頁到內存中;但當 B 再讀 C 的相同頁面時,雖然也會產生缺頁異常,但是不再需要從磁盤中複製文件過來,而可直接使用已經保存在內存中的文件數據。

3、可用於實現高效的大規模數據傳輸。

內存空間不足,是制約大數據操作的一個方面,解決方案往往是藉助硬盤空間協助操作,補充內存的不足。但是進一步會造成大量的文件 I/O 操作,極大影響效率。這個問題可以通過 mmap 映射很好的解決。換句話說,但凡是需要用磁盤空間代替內存的時候,mmap 都可以發揮其功效。

sendfile

3 次數據拷貝,其中 CPU 拷貝一次 1 次系統調用 2 次用戶態與內核態的上下文切換

在 Linux 內核版本 2.1 中,提供了一個專門發送文件的系統調用函數 sendfile。

首先,它可以替代前面的 read() 和 write() 這兩個系統調用,這樣就可以減少一次系統調用,也就減少了 2 次上下文切換的開銷。

其次,該系統調用,可以直接把內核緩衝區裏的數據拷貝到 socket 緩衝區裏,不再拷貝到用戶態,這樣就減少了一次數據拷貝,

現在一共只有 2 次上下文切換,和 3 次數據拷貝。如下圖:

但是這還不是真正的零拷貝技術。

真正的零拷貝

2次數據拷貝,無CPU參與拷貝
1次系統調用
2 次用戶態與內核態的上下文切換

從 Linux 內核 2.4 版本開始起,sendfile() 系統調用的過程發生了點變化,具體過程如下:

所以,這個過程之中,只進行了 2 次數據拷貝,如下圖:

性能如何?

所以,總體來看,零拷貝技術可以把文件傳輸的性能提高至少一倍以上。

kafka 和 Nginx 都使用了零拷貝技術

爲什麼需要內核緩存區?


現在回過頭再來看,爲什麼不直接將磁盤數據拷貝到網卡,而要在中間加一個內核緩存區呢?——核心原因是磁盤讀寫太慢了

內核緩存區做了什麼?

1、緩存最近被訪問的數據

最近訪問過的數據接下來很可能還會被訪問,所以利用 PageCache 緩存最近被訪問的數據,讀磁盤數據的時候,優先在 PageCache 找,如果數據存在則可以直接返回;如果沒有,則從磁盤中讀取,然後緩存在 PageCache 中。當 PageCache 的空間不足時,淘汰最久未被訪問的緩存。

2、預讀功能

利用空間局部性原理,假設 read 方法每次只會讀 32 KB 的字節,雖然 read 剛開始只會讀 0 ~ 32 KB 的字節,但內核會把其後面的 32~64 KB 也讀取到 PageCache,這樣後面讀取 32~64 KB 的成本就很低,如果在 32~64 KB 淘汰出 PageCache 前,進程讀取到它了,收益就非常大。

這兩個做法都在於解決讀寫磁盤相比讀寫內存的速度慢太多了這一痛點,大大提高了讀寫磁盤的性能。

所以零拷貝使用 內核緩存區技術進一步提升性能。

但是由於內核緩存區不適合傳輸大文件,所以零拷貝不適合傳輸大文件 因爲每當用戶訪問這些大文件的時候,內核就會把它們載入 內核緩存區中,於是 內核緩存區空間很快被這些大文件佔滿。其他「熱點」的小文件可能就無法充分使用到 內核緩存區,於是這樣磁盤讀寫的性能就會下降了;

所以,內核緩存區中的大文件數據,不但沒有享受到緩存帶來的好處,卻還耗費 DMA 多拷貝到 內核緩存區一次;

那針對大文件的傳輸,我們應該使用什麼方式呢?

大文件傳輸:異步 IO + 直接 IO


回顧最初的例子,當調用 read 方法讀取文件時,進程實際上會阻塞在 read 方法調用,因爲要等待磁盤數據的返回,如下圖:

對於阻塞的問題,可以用異步 I/O 來解決,它的工作方式如下圖:

可以發現,異步 I/O 並沒有涉及到 內核緩存區。

繞開 內核緩存區的 I/O 叫直接 I/O,使用 內核緩存區的 I/O 則叫緩存 I/O。通常,對於磁盤,異步 I/O 只支持直接 I/O。

所以,針對大文件的傳輸的方式,應該使用異步 I/O + 直接 I/O 來替代零拷貝技術。

總結


DMA 和傳統 IO

早期 I/O 操作,內存與磁盤的數據傳輸的工作都是由 CPU 完成的,而此時 CPU 不能執行其他任務,會特別浪費 CPU 資源。

於是,爲了解決這一問題,DMA 技術就出現了,實際數據傳輸工作由 DMA 控制器來完成,CPU 不需要參與數據傳輸的工作。

零拷貝

傳統 IO 的工作方式,從硬盤讀取數據,然後再通過網卡向外發送,需要進行 4 次上下文切換,和 4 次數據拷貝,更糟糕的是其中兩次都是 CPU 完成的。

爲了提高文件傳輸的性能,於是就出現了零拷貝技術,只有一個 sendfile 系統調用導致的 2 次用戶態與內核態的上下文切換,只進行了 2 次數據拷貝 (磁盤——pageCache——網卡),全程沒有通過 CPU 來搬運數據,所有的數據都是通過 DMA 來進行傳輸的。

需要注意的是,零拷貝技術中,數據沒有進入用戶緩衝區,所以用戶進程無法對文件內容作進一步的加工的,比如壓縮數據再發送。

內核緩存區

零拷貝技術是基於 內核緩存區的,內核緩存區具有

提升了訪問緩存數據的性能,解決了磁盤 IO 慢的問題,進一步提升了零拷貝的性能。

大文件傳輸

當傳輸大文件時,不能使用零拷貝,因爲可能由於 內核緩存區 被大文件佔據,而導致其他的「熱點」小文件無法利用到 內核緩存區,並且大文件的緩存命中率不高,這時就需要使用「異步 IO + 直接 IO 」的方式。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Y6fNqsS6QRIfl13BrreioQ