Linux系統很重要的一個性能提升點就是它的Pagecache, 因爲內存比IO快太多了,所以大家都想進辦法來利用這個cache。 文件系統也不例外,爲了達到高性能,文件讀取通常採用預讀來預測用戶的行爲,把用戶可能需要的數據預先讀取到cache去,達到高性能的目的。
Linux各個發行版readahead的實現差異很大,我們這裏重點討論2.6.18, RHEL 5U4發行版的行爲.文件預讀的實現主要在mm/readahead.c中,代碼才603行。 預讀的流程大概是這樣的,用戶需要文件頁面的時候入口函數do_generic_mapping_read會委託 page_cache_readahead來進行處理。它首先判斷用戶的IO是順序的還是隨機的,如果是隨機的就沒啥好預讀. 如果是順序的話,那麼預讀算法會根據用戶上一次讀取的頁面的使用情況評估出預讀的窗口,決定要讀多少頁面。讀頁面的模塊會先檢查要讀取頁面在 pagecache裏面是否已經存在,如果不存在的話就需要發起IO請求,讀取相應的頁面。
這個預讀的關鍵參數有3個: 用戶的req_size, 預讀算法評估出來的nr_to_read,以及實際上IO讀取的頁面數actual。
接下來我們就是要查看系統是如何運作的,所以我首先寫了個systemtap腳本叫做ratop.stp來獲取這些數據:
<pre class="prettyprint <a href=" http:="" www.ahlinux.com="" perl="" "="" target="_blank" style="margin-top: 0px; margin-bottom: 0px; padding: 0px;">perl"> $ uname -r2.6.18-164.el5 $ rpm -i kernel-debuginfo-common-2.6.18-164.el5.x86_64.rpm $ rpm -i kernel-debuginfo-2.6.18-164.el5.x86_64.rpm $ cat > ratop.stp#!/usr/bin/stap -DMAXMAPENTRIES=10240global total, skip global req, to_read, actual global __inode_filename probe kernel.function("page_cache_readahead") { ino = __file_ino($filp) req[ino]+=$req_size; total++;if($ra->flags & 0x2) skip++; } probe kernel.function("__do_page_cache_readahead").return{ ino = __file_ino($filp) to_read[ino]+= $nr_to_read;if($return>0) actual[ino]+=$return; } probe timer.ms(5000) {if(total) {foreach( ino in req-) { s0+= req[ino]; s1+= to_read[ino] s2+= actual[ino]; }printf("\\n%25s, %5s%6d, %5s%6d, %5s%8d, %5s%8d, %5s%8d\\n\\n", ctime(gettimeofday_s()), "TOTAL:", total, "SKIP:", skip, "REQ:",s0, "TO_RD:",s1, "NR_RD:",s2 ) /* print header */ printf("%25s %8s %8s %8s\\n", "FILENAME","REQ","TO_RD","NR_RD") foreach( ino in req- limit 20) printf("%25s %8d %8d %8d\\n", find_filename(ino), req[ino], to_read[ino], actual[ino]); }delete total;delete skip;delete req;delete to_read;delete actual; } probe generic.fop.open{ __inode_filename[ino]= filename } function find_filename(ino) {return __inode_filename[ino]==""?sprint(ino):__inode_filename[ino]; } probe begin { println("::"); } CTRL +D $ chmod +x ratop.stp $ sudo ./ratop.stp :: Tue May 31 05:41:37 2011, TOTAL: 2321, SKIP: 0, REQ: 6308, TO_RD: 6308, NR_RD: 1424 FILENAME REQ TO_RD NR_RD 056878.sst 15 15 0 062889.sst 13 13 6 .. 其中各個參數含義解釋如下: TOTAL: 系統共調用了多少次預讀 SKIP: 由於頁面在PAGECACHE中存在,略過多少次預讀 REQ: 用戶準備讀取的頁面數 TO_RD:預讀算法告訴我們要讀取的頁面數 NR_RD:實際IO系統讀取的頁面數 這個腳本每5秒打印下系統目前的預讀情況。
好吧,有了這個工具我們就可以做實驗了。
先在一個終端下運行我們的腳本:
$ sudo ./ratop.stp :: #等着出數據...
然後在另外一個終端下做實驗:
#準備個數據文件$ dd if=/dev/zero of=test count=1024 bs=40961024+0 records in1024+0 records out4194304 bytes (4.2 MB) copied, 0.008544 seconds, 491 MB/s#清空pagecache$ sudo sysctl vm.drop_caches=3vm.drop_caches = 3#第一次拷貝$ cp test junk && sleep 5#第二次拷貝$ cp test junk
我們就可以在之前的腳本窗口裏看到下面的信息:
#第一次拷貝test,我們可以看到 用戶要1025個頁面,預讀決定讀1084,但是實際IO讀了1024,很合理,因爲當時pagecache是空的 Tue May 31 05:50:21 2011, TOTAL: 1038, SKIP: 0, REQ: 1039, TO_RD: 1320, NR_RD: 1109 FILENAME REQ TO_RD NR_RD test 1025 1084 1024 cp 3 36 18 ... #第二次拷貝test,我們可以看到 用戶要1025個頁面,預讀決定讀284,但是實際IO讀了0,很合理,因爲所有的頁面在pagecache裏面都已經存在 Tue May 31 05:50:46 2011, TOTAL: 1038, SKIP: 804, REQ: 1039, TO_RD: 328, NR_RD: 0 FILENAME REQ TO_RD NR_RD test 1025 284 0 cp 3 4 0 ...
Linux系統不僅爲文件的讀取提供自動預讀,還提供了readahead這樣的系統調用和工具,幫助用戶主動預加載數據,我們演示下:
$ readahead junkPreloaded 0 files (0 KB) in 5 ms
另外一個窗口說:
Tue May 31 05:57:45 2011, TOTAL: 1044, SKIP: 805, REQ: 1045, TO_RD: 348, NR_RD: 0 FILENAME REQ TO_RD NR_RD junk 1026 284 0 readahead 3 4 0
Linux還支持對每個設備設定預讀的默認大小,不同的大小可以用來控制預讀的力度,用戶可以自行改變:
$ pwd /sys/block/sda/queue$ cat read_ahead_kb128$ echo 256 |sudo tee read_ahead_kb256
後續我會用這個工具分析leveldb數據庫的行爲,歡迎關注!
總結: 如果actual讀比用戶req的要多很多, 那麼我們的很多預讀就浪費了,可以考慮減少預讀的大小。
本文來自:愛好Linux技術網