1. 程式人生 > 其它 >磁碟I/O的三種方式對比:標準I/O、直接 I/O、mmap

磁碟I/O的三種方式對比:標準I/O、直接 I/O、mmap

文章目錄

@[TOC] 1.引入
1.1 標準I/O
1.2直接I/O
1.3 mmap


1.引入

讓我們先看一下Linux系統下IO結構模型

從圖中可以看到標準 I/O、mmap、直接 I/O 這三種 I/O 方式在流程上的差異
1.1 標準I/O

大多數檔案系統的預設I/O操作都是標準I/O。在Linux的快取I/O機制中,資料先從磁碟複製到核心空間的緩衝區,然後從核心空間緩衝區複製到應用程式的地址空間。
讀操作:作業系統檢查核心的緩衝區有沒有需要的資料,如果已經快取了,那麼就直接從快取中返回;否則從磁碟中讀取,然後快取在作業系統的快取中。
寫操作:將資料從使用者空間複製到核心空間的快取中。這時對使用者程式來說寫操作就已經完成,至於什麼時候再寫到磁碟中由作業系統決定,除非顯示地呼叫了sync等同步命令。
快取I/O的優點:1)在一定程度上分離了核心空間和使用者空間,保護系統本身的執行安全;2)可以減少讀盤的次數,從而提高效能。
快取I/O的缺點:資料在傳輸過程中需要在應用程式地址空間和快取之間進行多次資料拷貝操作,這些資料拷貝操作所帶來的CPU以及記憶體開銷是非常大的。

下面我們read()操作為例,下圖是read操作時流程


它會導致資料先從磁碟拷貝到 Page Cache 中,然後再從 Page Cache 拷貝到應用程式的使用者空間,這樣就會多一次記憶體拷貝。系統這樣設計主要是因為記憶體相對磁碟是高速裝置,即使多拷貝 100 次,記憶體也比真正讀一次硬碟要快。

寫操作

前面提到寫操作講資料從使用者控制元件複製到核心空間的快取中,資料什麼時候寫到磁碟由應用程式採用的寫操作機制決定,預設是採用延遲寫機制,應用程式只需要將資料寫到頁快取就可以了,完全不需要等待資料全部被寫入磁碟,系統會負責定期將頁快取資料寫入磁碟。

從中可以看出來,快取 I/O 可以很大程度減少真正讀寫磁碟的次數,從而提升效能。但是延遲寫機制可能會導致資料丟失,那系統究竟會在什麼時機真正把頁快取的資料寫入磁碟呢?

Page Cache 中被修改的記憶體稱為“髒頁”,核心通過 flush 執行緒定期將資料寫入磁碟。具體寫入的條件我們可以通過 /proc/sys/vm 檔案或者sysctl -a | grep vm 命令得到。

在實際應用中,如果某些資料我們覺得非常重要,是完全不允許有丟失風險的,這個時候我們應該採用同步寫機制。在應用程式中使用 sync、fsync、msync 等系統呼叫時,核心都會立刻將相應的資料寫回到磁碟。
1.2直接I/O

直接IO就是應用程式直接訪問磁碟資料,而不經過核心緩衝區,這樣做的目的是減少一次從核心緩衝區到使用者程式快取的資料複製。比如說資料庫管理系統這類應用,它們更傾向於選擇它們自己的快取機制,因為資料庫管理系統往往比作業系統更瞭解資料庫中存放的資料,資料庫管理系統可以提供一種更加有效的快取機制來提高資料庫中資料的存取效能。
直接IO的缺點:如果訪問的資料不在應用程式快取中,那麼每次資料都會直接從磁碟載入,這種直接載入會非常耗時。通常直接IO與非同步IO結合使用,會得到比較好的效能。(非同步IO:當訪問資料的執行緒發出請求之後,執行緒會接著去處理其他事,而不是阻塞等待)

直接I/O流程


從圖中你可以看到,直接 I/O 訪問檔案方式減少了一次資料拷貝和一些系統呼叫的耗時,很大程度降低了 CPU 的使用率以及記憶體的佔用。
但是直接與磁碟互動非常耗時,所以只有確定標準I/O開銷非常巨大才考慮使用直接I/O。
1.3 mmap

mmap是指將硬碟上檔案的位置與程序邏輯地址空間中一塊大小相同的區域一一對應,當要訪問記憶體中一段資料時,轉換為訪問檔案的某一段資料。這種方式的目的同樣是減少資料在使用者空間和核心空間之間的拷貝操作。當大量資料需要傳輸的時候,採用記憶體對映方式去訪問檔案會獲得比較好的效率。
使用記憶體對映檔案處理儲存於磁碟上的檔案時,將不必再對檔案執行I/O操作,這意味著在對檔案進行處理時將不必再為檔案申請並分配快取,所有的檔案快取操作均由系統直接管理,由於取消了將檔案資料載入到記憶體、資料從記憶體到檔案的回寫以及釋放記憶體塊等步驟,使得記憶體對映檔案在處理大資料量的檔案時能起到相當重要的作用。

mmap的優點:

減少系統呼叫。我們只需要一次 mmap() 系統呼叫,後續所有的呼叫像操作記憶體一樣,而不會出現大量的 read/write 系統呼叫。

減少資料拷貝。普通的 read() 呼叫,資料需要經過兩次拷貝;而 mmap 只需要從磁碟拷貝一次就可以了,並且由於做過記憶體對映,也不需要再拷貝回用戶空間。

可靠性高。mmap 把資料寫入頁快取後,跟快取 I/O 的延遲寫機制一樣,可以依靠核心執行緒定期寫回磁碟。但是需要提的是,mmap 在核心崩潰、突然斷電的情況下也一樣有可能引起內容丟失,當然我們也可以使用 msync來強制同步寫。

從上面的圖看來,我們使用 mmap 僅僅只需要一次資料拷貝。看起來 mmap 的確可以秒殺普通的檔案讀寫,那我們為什麼不全都使用 mmap 呢?事實上,它也存在一些缺點:

虛擬記憶體增大。mmap 會導致虛擬記憶體增大,我們的 APK、Dex、so 都是通過 mmap 讀取。而目前大部分的應用還沒支援 64 位,除去核心使用的地址空間,一般我們可以使用的虛擬記憶體空間只有 3GB 左右。如果 mmap 一個 1GB 的檔案,應用很容易會出現虛擬記憶體不足所導致的 OOM。
磁碟延遲。mmap 通過缺頁中斷向磁碟發起真正的磁碟 I/O,所以如果我們當前的問題是在於磁碟 I/O 的高延遲,那麼用 mmap() 消除小小的系統呼叫開銷是杯水車薪的。

在 Android 中可以將檔案通過MemoryFile或者MappedByteBuffer對映到記憶體,然後進行讀寫,使用這種方式對於小檔案和頻繁讀寫操作的檔案還是有一定優勢的。我通過簡單程式碼測試,測試結果如下。



從上面的資料看起來 mmap 好像的確跟寫記憶體的效能差不多,但是這並不正確,因為我們並沒有計算檔案系統非同步落盤的耗時。在低端機或者系統資源嚴重不足的時候,mmap 也一樣會出現頻繁寫入磁碟,這個時候效能就會出現快速下降。

mmap 比較適合於對同一塊區域頻繁讀寫的情況,推薦也使用執行緒來操作。使用者日誌、資料上報都滿足這種場景,另外需要跨程序同步的時候,mmap 也是一個不錯的選擇。Android 跨程序通訊有自己獨有的 Binder 機制,它內部也是使用 mmap 實現。

利用 mmap,Binder 在跨程序通訊只需要一次資料拷貝,比傳統的 Socket、管道等跨程序通訊方式會少一次資料拷貝。

https://www.cnblogs.com/longchang/p/11162813.html ------------------越是喧囂的世界,越需要寧靜的思考------------------ 合抱之木,生於毫末;九層之臺,起於壘土;千里之行,始於足下。 積土成山,風雨興焉;積水成淵,蛟龍生焉;積善成德,而神明自得,聖心備焉。故不積跬步,無以至千里;不積小流,無以成江海。騏驥一躍,不能十步;駑馬十駕,功在不捨。鍥而舍之,朽木不折;鍥而不捨,金石可鏤。蚓無爪牙之利,筋骨之強,上食埃土,下飲黃泉,用心一也。蟹六跪而二螯,非蛇鱔之穴無可寄託者,用心躁也。