1. 程式人生 > >系統技術非業餘研究 » Linux檔案預讀分析以及評估對系統的影響

系統技術非業餘研究 » Linux檔案預讀分析以及評估對系統的影響

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請求,讀取相應的頁面。還有個路徑就是在檔案mmap缺頁的時候filemap_nopage呼叫do_page_cache_readahead進行預讀, 不過這個路徑在通常的環境裡概率不高.

這個預讀的關鍵引數有3個: 使用者的req_size, 預讀演算法評估出來的nr_to_read,以及實際上IO讀取的頁面數actual。

接下來我們就是要檢視系統是如何運作的,所以我首先寫了個systemtap指令碼叫做ratop.stp來獲取這些資料:

$ uname -r
2.6.18-164.el5
 
$ sudo rpm -i kernel-debuginfo-common-2.6.18-164.el5.x86_64.rpm
$ sudo rpm -i kernel-debuginfo-2.6.18-164.el5.x86_64.rpm  

$ cat > ratop.stp
#!/usr/bin/stap -DMAXMAPENTRIES=10240 -DMAXACTION=9999

global total, skip
global req, to_read, actual
global __inode_filename

//generic_file_aio_read path
probe kernel.function("page_cache_readahead") {
ino = __file_ino($filp)
req[ino] += $req_size;
total++;
if($ra->flags & 0x2) skip++;
}

//mmap path
probe kernel.function("do_page_cache_readahead") {
ino = __file_ino($filp)
req[ino]+=$nr_to_read;
}

//
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 timer.s(180) {
delete __inode_filename
}

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=4096
1024+0 records in
1024+0 records out
4194304 bytes (4.2 MB) copied, 0.008544 seconds, 491 MB/s
#清空pagecache
$ sudo sysctl vm.drop_caches=3                                         
vm.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這樣的系統呼叫和工具,幫助使用者主動預載入資料,我們演示下:

$ sudo sysctl vm.drop_caches=3                                         
vm.drop_caches = 3
$ cat > file.lst
test
junk
CTRL+D
$ readahead file.lst 
Preloaded 2 files (409600 KB) in 863 ms
$ cp test /dev/null

另外一個視窗說:

# 說明檔案確實被預載入了。
Tue May 31 08:51:08 2011,  TOTAL:  1038, SKIP:   804,  REQ:    1039, TO_RD:     392, NR_RD:      29

                 FILENAME      REQ    TO_RD    NR_RD
                     test     1025      284        0
                     cp        3       36       18

Linux還支援對每個裝置設定預讀的預設大小,不同的大小可以用來控制預讀的力度,使用者可以自行改變:

$ pwd
/sys/block/sda/queue
$ cat read_ahead_kb 
128
$ echo 256 |sudo tee  read_ahead_kb 
256

後續我會用這個工具分析leveldb資料庫的行為,歡迎關注!

總結: 如果actual讀比使用者req的要多很多, 那麼我們的很多預讀就浪費了,可以考慮減少預讀的大小。

玩得開心!

PS. 附上不分檔案統計的版本,方便使用:

$ cat ra.stp
global req, to_read, actual

//generic_file_aio_read path
probe kernel.function("page_cache_readahead") {
  req += $req_size;
}

//mmap path
probe kernel.function("do_page_cache_readahead") {
  req += $nr_to_read;
}

//
probe kernel.function("__do_page_cache_readahead").return {
  to_read += $nr_to_read;
  if($return>0) actual += $return;
}

probe timer.s(5) {
  if(req) {
    printf("%25s, %5s%8d, %5s%8d, %5s%8d\n",
        ctime(gettimeofday_s()),
        "REQ:", req,
        "TO_RD:", to_read,
        "NR_RD:", actual
        )
    delete req;
    delete to_read;
    delete actual;
  }
}

probe begin {
  println("::");
}

Post Footer automatically generated by wp-posturl plugin for wordpress.