Linux檔案讀寫機制及優化方式

2021-08-05 20:01:22 字數 4079 閱讀 8221

本文只討論linux下檔案的讀寫機制,不涉及不同讀取方式如read,fread,cin等的對比,這些讀取方式本質上都是呼叫系統api read,只是做了不同封裝。以下所有測試均使用open, read, write這一套系統api

快取是用來減少高速裝置訪問低速裝置所需平均時間的元件,檔案讀寫涉及到計算機記憶體和磁碟,記憶體操作速度遠遠大於磁碟,如果每次呼叫read,write都去直接操作磁碟,一方面速度會被限制,一方面也會降低磁碟使用壽命,因此不管是對磁碟的讀操作還是寫操作,作業系統都會將資料快取起來

頁快取(page cache)是位於記憶體和檔案之間的緩衝區,它實際上也是一塊記憶體區域,所有的檔案io(包括網路檔案)都是直接和頁快取互動,作業系統通過一系列的資料結構,比如inode, address_space, struct page,實現將乙個檔案對映到頁的級別,這些具體資料結構及之間的關係我們暫且不討論,只需知道頁快取的存在以及它在檔案io中扮演著重要角色,很大一部分程度上,檔案讀寫的優化就是對頁快取使用的優化

頁快取對應檔案中的一塊區域,如果頁快取和對應的檔案區域內容不一致,則該頁快取叫做髒頁(dirty page)。對頁快取進行修改或者新建頁快取,只要沒有刷磁碟,都會產生髒頁

linux上有兩種方式檢視頁快取大小,一種是free命令

$ free

total used free shared buffers cached

mem: 20470840 1973416 18497424 164 270208 1202864

-/+ buffers/cache: 500344 19970496

swap: 0 0 0

cached那一列就是頁快取大小,單位byte

另一種是直接檢視/proc/meminfo,這裡我們只關注兩個字段

cached:          1202872 kb

dirty: 52 kb

cached是頁快取大小,dirty是髒頁大小

linux有一些引數可以改變作業系統對髒頁的回寫行為

$ sysctl -a 2>/dev/null | grep dirty

vm.dirty_background_ratio = 10

vm.dirty_background_bytes = 0

vm.dirty_ratio = 20

vm.dirty_bytes = 0

vm.dirty_writeback_centisecs = 500

vm.dirty_expire_centisecs = 3000

vm.dirty_background_ratio是記憶體可以填充髒頁的百分比,當髒頁總大小達到這個比例後,系統後台程序就會開始將髒頁刷磁碟(vm.dirty_background_bytes類似,只不過是通過位元組數來設定)

vm.dirty_ratio是絕對的髒資料限制,記憶體裡的髒資料百分比不能超過這個值。如果髒資料超過這個數量,新的io請求將會被阻擋,直到髒資料被寫進磁碟

vm.dirty_writeback_centisecs指定多長時間做一次髒資料寫回操作,單位為百分之一秒

vm.dirty_expire_centisecs指定髒資料能存活的時間,單位為百分之一秒,比如這裡設定為30秒,在作業系統進行寫回操作時,如果髒資料在記憶體中超過30秒時,就會被寫回磁碟

這些引數可以通過sudo sysctl -w vm.dirty_background_ratio=5這樣的命令來修改,需要root許可權,也可以在root使用者下執行echo 5 > /proc/sys/vm/dirty_background_ratio來修改

在有了頁快取和髒頁的概念後,我們再來看檔案的讀寫流程

使用者read呼叫完成

使用者write呼叫完成

頁被修改後成為髒頁,作業系統有兩種機制將髒頁寫回磁碟 

頁快取和磁碟檔案是有對應關係的,這種關係由作業系統維護,對頁快取的讀寫操作是在核心態完成,對使用者來說是透明的

不同的優化方案適應於不同的使用場景,比如檔案大小,讀寫頻次等,這裡我們不考慮修改系統引數的方案,修改系統引數總是有得有失,需要選擇乙個平衡點,這和業務相關度太高,比如是否要求資料的強一致性,是否容忍資料丟失等等。優化的思路有以下兩個考慮點

最大化利用頁快取

減少系統api呼叫次數

第一點很容易理解,盡量讓每次io操作都命中頁快取,這比操作磁碟會快很多,第二點提到的系統api主要是read和write,由於系統呼叫會從使用者態進入核心態,並且有些還伴隨這記憶體資料的拷貝,因此在有些場景下減少系統呼叫也會提高效能

readahead是一種非阻塞的系統呼叫,它會觸發作業系統將檔案內容預讀到頁快取中,並且立馬返回,函式原型如下

ssize_t readahead(int fd, off64_t offset, size_t count);
在通常情況下,呼叫readahead後立馬呼叫read並不會提高讀取速度,我們通常在批量讀取或在讀取之前一段時間呼叫readahead,假設如下場景,我們需要連續讀取1000個1m的檔案,有如下兩個方案,偽**如下

直接呼叫read函式

char* buf = (char*)malloc(10*1024*1024);

for (int i = 0; i < 1000; ++i)

先批量呼叫readahead再呼叫read

int* fds = (int*)malloc(sizeof(int)*1000);

int* fd_size = (int*)malloc(sizeof(int)*1000);

for (int i = 0; i < 1000; ++i)

char* buf = (char*)malloc(10*1024*1024);

for (int i = 0; i < 1000; ++i)

感興趣的可以寫**實際測試一下,需要注意的是在測試前必須先回寫髒頁和清空頁快取,執行如下命令

sync && sudo sysctl -w vm.drop_caches=3
可通過檢視/proc/meminfo中的cached及dirty項確認是否生效

通過測試發現,第二種方法比第一種讀取速度大約提高10%-20%,這種場景下是批量執行readahead後立馬執行read,優化空間有限,如果有一種場景可以在read之前一段時間呼叫readahead,那將大大提高read本身的讀取速度

這種方案實際上是利用了作業系統的頁快取,即提前觸發作業系統將檔案讀取到頁快取,並且作業系統對缺頁處理、快取命中、快取淘汰都由一套完善的機制,雖然使用者也可以針對自己的資料做快取管理,但和直接使用頁快取比並沒有多大差別,而且會增加維護代價

mmap是一種記憶體對映檔案的方法,即將乙個檔案或者其它物件對映到程序的位址空間,實現檔案磁碟位址和程序虛擬位址空間中一段虛擬位址的一一對映關係,函式原型如下

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
實現這樣的對映關係後,程序就可以採用指標的方式讀寫操作這一段記憶體,而系統會自動回寫髒頁面到對應的檔案磁碟上,即完成了對檔案的操作而不必再呼叫read,write等系統呼叫函式。如下圖所示

mmap除了可以減少read,write等系統呼叫以外,還可以減少記憶體的拷貝次數,比如在read呼叫時,乙個完整的流程是作業系統讀磁碟檔案到頁快取,再從頁快取將資料拷貝到read傳遞的buffer裡,而如果使用mmap之後,作業系統只需要將磁碟讀到頁快取,然後使用者就可以直接通過指標的方式操作mmap對映的記憶體,減少了從核心態到使用者態的資料拷貝

mmap適合於對同一塊區域頻繁讀寫的情況,比如乙個64m的檔案儲存了一些索引資訊,我們需要頻繁修改並持久化到磁碟,這樣可以將檔案通過mmap對映到使用者虛擬記憶體,然後通過指標的方式修改記憶體區域,由作業系統自動將修改的部分刷回磁碟,也可以自己呼叫msync手動刷磁碟

Linux檔案讀寫機制及優化方式

本文只討論linux下檔案的讀寫機制,不涉及不同讀取方式如read,fread,cin等的對比,這些讀取方式本質上都是呼叫系統api read,只是做了不同封裝。以下所有測試均使用open,read,write這一套系統api 快取 快取是用來減少高速裝置訪問低速裝置所需平均時間的元件,檔案讀寫涉及...

Python檔案讀寫機制

python提供了必要的函式和方法進行預設情況下的檔案基本操作 檔案開啟方式 open name mode buf name 檔案路徑 mode 開啟方式 buf 緩衝buffering大小 檔案讀取方式 read size 讀取檔案 讀取size位元組,預設讀取全部 readline size 讀...

Shuffle機制及優化

map方法之後,reduce方法之前的資料處理過程稱之為shuffle。具體shuffle過程詳解 1 maptask收集我們的map 方法輸出的kv對,放到記憶體緩衝區中 2 從記憶體緩衝區不斷溢位本地磁碟檔案,可能會溢位多個檔案 3 多個溢位檔案會被合併成大的溢位檔案 4 在溢位過程及合併的過程...