iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 5
5
Software Development

30天之從 0 至 1 盡可能的建立一個好的系統 (性能基礎篇)系列 第 5

30-05 之應用層的 I / O 加速 - 零拷貝 ( Zero Copy )

  • 分享至 

  • xImage
  •  

黑色好看版 - 傳送門


https://ithelp.ithome.com.tw/upload/images/20190920/20089358mvx7DO8cN9.png

正文開始


前二篇文章中,咱們已經學習完運算方面的優化,而接下來幾篇文章,咱們要來說明 I/O 優化這個議題。

I/O 基本上可以分為兩種,『 文件 I/O 』與『 網路 I/O 』,這兩種 I/O 操作原理大同小議,但是優化方式卻有些不同,接下來這一篇文章,算是混合。

本篇文章分為以下幾個章節 :

  • I/O 原理。
  • 零拷貝 I/O 的概念與優化原理。
  • 零拷貝的實現。
  • 零拷貝的語言支援與問題探討。

I/O 原理


先從最基本的來看,何謂 I/O ?

I/O ( Input/Output) 通常是指資料在『系統』與『外部裝置』的輸入與輸入,最簡單的例子,抓取硬碟或 USB 資料就是所謂的 I/O 操作。

接下來我們簡單的來看一下,在 linux 操作系統上,所謂的『 從硬碟讀取資料,並結果輸出到網路 』到底是在做什麼。

記憶體層面來看 I/O 流程

記憶體就是作業系統最基本的資料儲放地,接下來我們從記憶體的層面來看,所謂的『從硬碟讀取資料,並將結果輸出到網路』它是如何變化的。

基本『 讀與寫 』流程如下 :

在看流程前先說明一下,等等到看到的所謂『 緩衝區 』 就是指在某個記憶體中,切割一些空間出來,來當緩衝區。像等等看到的內核緩衝區就是在內核的記憶體中,拿一些空間來儲放等等要進來的資料。

接下來就開始看流程。

  1. 應用程式發送 system call read 某個檔案給作業系統的內核。(用戶切內核)
  2. 內核看看內核緩衝區有沒有相關資料。
  3. 有則將內核緩衝區的資料,拷貝到用戶緩衝區。
  4. 無則前網硬碟取得。
  5. 將硬碟資料拷貝至內核緩存區。
  6. 將內核緩衝區資料,拷貝到用戶緩衝區。(內核切成用戶)
  7. 在從用戶緩衝區發送 system call write 將用戶記憶體資料拷貝到 socket 緩衝區,這緩衝區也是在內核中。(用戶切內核)
  8. 用戶端收到 ack (內核切用戶)
  9. 資料飛向世界。

順到說一下,這樣的操作總共會進行 4 次的上下文切換。

https://ithelp.ithome.com.tw/upload/images/20190920/20089358saFrLM8Jy3.png
圖 4 : 作業系統 I/O 流程

~ 小知識 ~

read 是 linux 所提供的操作,它可以從 file descriptor 讀取資料,而 fd 是每一次建立檔案或網路連結都會產生的東東。通常我們大部份使用的語言,都有提供呼叫它的接口。

write 也是 linux 所提供的操作,它可以寫入資料至 file descriptor,但這裡有一個重點要注意,它是寫入到內核裡的緩衝區後就會回 ack ,而不是從網路發送出去後,或寫到硬碟後才回 ack 。

以上兩個操作都是所謂的 system call 。

零拷貝 I/O 的概念


上面理解完 I/O 的一些原理以,就下來我們來看看,能提升效率的地方在那 ?

假設我們有一個操作,它的過程如下 :

從硬碟讀取資料後,直接輸出到網路上 (不需特別處理)。

那這種它的傳統記憶體處理流程如下,就如下圖 5 一樣所示 :

https://ithelp.ithome.com.tw/upload/images/20190920/20089358Kwg3n74L0U.png
圖 5 : 作業系統讀取硬碟發給網路流程

而其中可以優化的地方在於『 內核空間 』那一塊的操作。

每當我們要從硬碟讀取資料時,要先將資料拷貝到內核緩衝區,接下來再拷貝到應用程式緩衝區,然後發送到網路時,要將應用程式記憶體資料再拷則到內核空間的 socket 緩衝區,最後再發送至網路上。

而零拷貼優化方向就在於如下圖 6 所示,如果我們可以不用再拷貝到用戶緩衝區的話,那不論是空間還是運算都省下不少工了。

https://ithelp.ithome.com.tw/upload/images/20190920/20089358TOfbhdsOrH.png
圖 6 : 零拷貝優化圖

而就是零拷貝

零拷貝的實現


linux 實現這零拷貝技術的所提供的兩個方法如下 :

  • mmap + write
  • sendfile

mmap + write

linux-mmap

在說明 mmap ( Memory-mapped )時,我們需要先理解一個東西,『 虛擬記憶體 』。

虛擬記憶體的概念如下圖,簡單的說,咱們應用程式記憶體存的不是實際的資料,而是存放對應到內核記憶體的位置,這個地方被稱為『 虛擬記憶體 』。你也可以想成應用程式記憶體與內核記憶體指向是同一個記憶體位置。

https://ithelp.ithome.com.tw/upload/images/20190920/20089358G5drv4nE8o.png
圖 7 : 虛擬記憶體概念

接下來下圖 8 為使用 mmap+write 實現零拷則的方式,當我們要 write 資料時,因為我們是存內核的記憶體位置,因此就不需要在進行拷貝,而直接將此位置資料,發送給網路世界。

  1. 使用 mmap 將讀取指定 fd ( file descriptor ) 的資料。
  2. 內核收到後,會去硬碟中抓資料。
  3. 將資料拷貝到內核緩衝區。
  4. 回傳資料在內核記憶體中什麼位置,給應用層。
  5. 內核將記憶體中指定位置的資料,拷貝到 socket 的緩衝區中。(兩個緩衝都在內核記憶體區)
  6. 發起 write 將指定內核記憶體位置的資料,傳送給網路。
  7. 資料飛向世界。

https://ithelp.ithome.com.tw/upload/images/20190920/20089358u3xdhZpgKf.png
圖 8 : 使用 mmap 實現零拷貝優化

sendfile

linux-sendfile

但是上面的方法還是需要運行 write,而它是所謂的 system call 方法,所以還是會讓進程或線程進行上下文切換。

因此後來又提供一個方法為『 sendfile 』,它的流程如下圖 9 所示 :

  1. 應用層發起 sendfile 請求。
  2. 內核取硬碟中讀取資料。
  3. 將資料拷貝到內核緩衝區。
  4. 在直接將資料拷貝到 socket 緩衝區。
  5. 飛向世界。

https://ithelp.ithome.com.tw/upload/images/20190920/20089358hlDIXVlyCQ.png
圖 9 : 使用 sendfile 實現零拷貝優化

零拷貝的語言支援與問題探討


接下來這一篇章節咱們簡單看看各種語言零拷貝的實現。

首先 java NIO 有提供 transferTo 可以實現零拷貝,它的方法如下 :

transferTo(position, count, writableChannel);

然後之後查了一下,java 的 Netty 看起來也有支援,但是有人又覺得不太一樣。由於我不是寫 java 的所以也不太懂,以下僅供參考。

对于 Netty ByteBuf 的零拷贝(Zero Copy) 的理解

nodejs 有一個『 Buffer 』 功能,這東西它可以在某個地方的記憶體,開啟一些緩衝區,而這個『 某個地方 』目前查了一下,它說 :

Nodejs-Buffer

A Buffer is similar to an array of integers but corresponds to a raw memory allocation outside the V8 heap.

它這裡很明確的說是在 v8 之外,這是我也只能猜是內核,那這裡就可能可以動動手腳了,然後網路上好像有找到 nodejs mmap 可以參考一下。

Mmap for Node.js

而 php 方面,有在 swoole 看到相對應的 mmap 方法,那這個應該也有機率可以實現。

The Swoole\Mmap class

問題

零拷貝使用上有個前提 :

那就是你要的資料不需要拉到應用端來修修改改。

所以事實上這也代表這,不太能使用需要應用端處理的東西,例如 https,但是像 tcp 這傳輸層級的東西就可以。

例如 redis 的 pubsub,它基本上是收到東西後,再直接透過 tcp 丟出去,這裡就有使用零拷貝技術。另外 kafka 被稱為高性能系統其中一個原因也在於使用了這一項技術。

結論與心得


這裡簡單說一下我的想法,基本上大部份的開發者都不太會碰到這個東東,而在 io 優化的情況下,除非是追求最高使用量的情況,那基本上可以先不往這方向優化,畢竟這不是所有語言都有支援。

不過我會把這章拉出來寫比較大的原因在於,觀念,這一篇文章帶入了很多關於我們常使用的 I/O 操作觀念,因此當你能理解零拷貝這東西後,基本上也代表你理解了 I/O 運行的原理,那這之後的路就會好走多囉。

參考資料



上一篇
30-04 之應用層的運算加速 - 並行運算
下一篇
30-06 之應用層的 I / O 優化 - Stream ( 與一些 IPC 知識 )
系列文
30天之從 0 至 1 盡可能的建立一個好的系統 (性能基礎篇)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
foreverbule2003
iT邦新手 5 級 ‧ 2019-10-05 17:06:33

這個系列寫得實在太精湛了!!!非常感謝你知識上的分享

馬克 iT邦研究生 2 級 ‧ 2019-10-07 07:58:47 檢舉

很高興可以幫到你 ~

我要留言

立即登入留言