17講案例篇:如何利⽤系統快取優化程式的運⾏效率
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD echo"deb https://repo.iovisor.org/apt/xenial xenial main" | sudo tee /etc/apt/sources.list.d/iovisor.list sudo apt-get update sudo apt-get install -y bcc-tools libbcc-examples linux-headers-$(uname -r)
centos安裝:
下載BCC的依賴: yum install -y elfutils-libelf-devel flex
安裝bcc bcc-tools:yum --enablerepo=elrepo-kernel install bcc bcc-tools
新增環境變數:export PATH=$PATH:/usr/share/bcc/tools
檢視:
[root@test ~]# ll /usr/share/bcc/tools/
total 844
-rwxr-xr-x 1 root root 34534 Jul 31 2019 argdist
-rwxr-xr-x 1 root root 2179 Jul 31 2019 bashreadline
-rwxr-xr-x 1 root root 6229 Jul 31 2019 biolatency
-rwxr-xr-x 1 root root 5522 Jul 31 2019 biosnoop
-rwxr-xr-x 1 root root 6391 Jul 31 2019 biotop
-rwxr-xr-x 1 root root 1150 Jul 31 2019 bitesize
-rwxr-xr-x 1 root root 2451 Jul 31 2019 bpflist
-rwxr-xr-x 1 root root 6330 Apr 1 2020 btrfsdist
-rwxr-xr-x 1 root root 9581 Apr 1 2020 btrfsslower
-rwxr-xr-x 1 root root 4715 Jul 31 2019 cachestat
-rwxr-xr-x 1 root root 7300 Jul 31 2019 cachetop
-rwxr-xr-x 1 root root 6291 Jul 31 2019 capable
-rwxr-xr-x 1 root root 57 Apr 1 2020 cobjnew
-rwxr-xr-x 1 root root 5125 Apr 1 2020 cpudist
-rwxr-xr-x 1 root root 14595 Jul 31 2019 cpuunclaimed
-rwxr-xr-x 1 root root 7093 Jul 31 2019 dbslower
-rwxr-xr-x 1 root root 3778 Jul 31 2019 dbstat
-rwxr-xr-x 1 root root 3936 Jul 31 2019 dcsnoop
-rwxr-xr-x 1 root root 3918 Jul 31 2019 dcstat
-rwxr-xr-x 1 root root 19928 Jul 31 2019 deadlock
-rw-r--r-- 1 root root 7087 Jul 31 2019 deadlock.c
drwxr-xr-x 3 root root 8192 May 17 10:29 doc
-rwxr-xr-x 1 root root 6828 Jul 31 2019 drsnoop
-rwxr-xr-x 1 root root 7252 Jul 31 2019 execsnoop
-rwxr-xr-x 1 root root 6490 Apr 1 2020 ext4dist
-rwxr-xr-x 1 root root 9916 Apr 1 2020 ext4slower
-rwxr-xr-x 1 root root 3616 Jul 31 2019 filelife
-rwxr-xr-x 1 root root 7319 Apr 1 2020 fileslower
-rwxr-xr-x 1 root root 6029 Jul 31 2019 filetop
-rwxr-xr-x 1 root root 12457 Jul 31 2019 funccount
-rwxr-xr-x 1 root root 7973 Jul 31 2019 funclatency
-rwxr-xr-x 1 root root 10124 Jul 31 2019 funcslower
-rwxr-xr-x 1 root root 3802 Jul 31 2019 gethostlatency
-rwxr-xr-x 1 root root 5195 Jul 31 2019 hardirqs
-rwxr-xr-x 1 root root 59 Apr 1 2020 javacalls
-rwxr-xr-x 1 root root 58 Apr 1 2020 javaflow
-rwxr-xr-x 1 root root 56 Apr 1 2020 javagc
-rwxr-xr-x 1 root root 60 Apr 1 2020 javaobjnew
-rwxr-xr-x 1 root root 58 Apr 1 2020 javastat
-rwxr-xr-x 1 root root 61 Apr 1 2020 javathreads
-rwxr-xr-x 1 root root 3406 Jul 31 2019 killsnoop
drwxr-xr-x 2 root root 88 May 17 10:29 lib
-rwxr-xr-x 1 root root 3689 Jul 31 2019 llcstat
-rwxr-xr-x 1 root root 2061 Jul 31 2019 mdflush
-rwxr-xr-x 1 root root 19032 Apr 1 2020 memleak
-rwxr-xr-x 1 root root 12645 Apr 1 2020 mountsnoop
-rwxr-xr-x 1 root root 3054 Jul 31 2019 mysqld_qslower
-rwxr-xr-x 1 root root 4726 Jul 31 2019 nfsdist
-rwxr-xr-x 1 root root 9032 Apr 1 2020 nfsslower
-rwxr-xr-x 1 root root 56 Apr 1 2020 nodegc
-rwxr-xr-x 1 root root 58 Apr 1 2020 nodestat
-rwxr-xr-x 1 root root 11775 Apr 1 2020 offcputime
-rwxr-xr-x 1 root root 14371 Apr 1 2020 offwaketime
-rwxr-xr-x 1 root root 2107 Apr 1 2020 oomkill
-rwxr-xr-x 1 root root 7219 Jul 31 2019 opensnoop
-rwxr-xr-x 1 root root 59 Apr 1 2020 perlcalls
-rwxr-xr-x 1 root root 58 Apr 1 2020 perlflow
-rwxr-xr-x 1 root root 58 Apr 1 2020 perlstat
-rwxr-xr-x 1 root root 58 Apr 1 2020 phpcalls
-rwxr-xr-x 1 root root 57 Apr 1 2020 phpflow
-rwxr-xr-x 1 root root 57 Apr 1 2020 phpstat
-rwxr-xr-x 1 root root 1137 Jul 31 2019 pidpersec
-rwxr-xr-x 1 root root 12752 Jul 31 2019 profile
-rwxr-xr-x 1 root root 61 Apr 1 2020 pythoncalls
-rwxr-xr-x 1 root root 60 Apr 1 2020 pythonflow
-rwxr-xr-x 1 root root 58 Apr 1 2020 pythongc
-rwxr-xr-x 1 root root 60 Apr 1 2020 pythonstat
-rwxr-xr-x 1 root root 3496 Jul 31 2019 reset-trace
-rwxr-xr-x 1 root root 59 Apr 1 2020 rubycalls
-rwxr-xr-x 1 root root 58 Apr 1 2020 rubyflow
-rwxr-xr-x 1 root root 56 Apr 1 2020 rubygc
-rwxr-xr-x 1 root root 60 Apr 1 2020 rubyobjnew
-rwxr-xr-x 1 root root 58 Apr 1 2020 rubystat
-rwxr-xr-x 1 root root 8051 Apr 1 2020 runqlat
-rwxr-xr-x 1 root root 7799 Jul 31 2019 runqlen
-rwxr-xr-x 1 root root 7072 Apr 1 2020 runqslower
-rwxr-xr-x 1 root root 7983 Jul 31 2019 shmsnoop
-rwxr-xr-x 1 root root 3635 Jul 31 2019 slabratetop
-rwxr-xr-x 1 root root 8246 Jul 31 2019 sofdsnoop
-rwxr-xr-x 1 root root 4116 Jul 31 2019 softirqs
-rwxr-xr-x 1 root root 6074 Apr 1 2020 solisten
-rwxr-xr-x 1 root root 7120 Jul 31 2019 sslsniff
-rwxr-xr-x 1 root root 15924 Jul 31 2019 stackcount
-rwxr-xr-x 1 root root 4621 Jul 31 2019 statsnoop
-rwxr-xr-x 1 root root 1264 Jul 31 2019 syncsnoop
-rwxr-xr-x 1 root root 6193 Jul 31 2019 syscount
-rwxr-xr-x 1 root root 58 Apr 1 2020 tclcalls
-rwxr-xr-x 1 root root 57 Apr 1 2020 tclflow
-rwxr-xr-x 1 root root 59 Apr 1 2020 tclobjnew
-rwxr-xr-x 1 root root 57 Apr 1 2020 tclstat
-rwxr-xr-x 1 root root 7868 Jul 31 2019 tcpaccept
-rwxr-xr-x 1 root root 7382 Jul 31 2019 tcpconnect
-rwxr-xr-x 1 root root 7361 Jul 31 2019 tcpconnlat
-rwxr-xr-x 1 root root 5839 Jul 31 2019 tcpdrop
-rwxr-xr-x 1 root root 16283 Jul 31 2019 tcplife
-rwxr-xr-x 1 root root 8770 Jul 31 2019 tcpretrans
-rwxr-xr-x 1 root root 7825 Apr 1 2020 tcpsubnet
-rwxr-xr-x 1 root root 9358 Jul 31 2019 tcptop
-rwxr-xr-x 1 root root 16438 Apr 1 2020 tcptracer
-rwxr-xr-x 1 root root 4157 Jul 31 2019 tplist
-rwxr-xr-x 1 root root 37908 Jul 31 2019 trace
-rwxr-xr-x 1 root root 3014 Jul 31 2019 ttysnoop
-rwxr-xr-x 1 root root 1381 Apr 1 2020 vfscount
-rwxr-xr-x 1 root root 2634 Jul 31 2019 vfsstat
-rwxr-xr-x 1 root root 6897 Jul 31 2019 wakeuptime
-rwxr-xr-x 1 root root 4550 Apr 1 2020 xfsdist
-rwxr-xr-x 1 root root 7882 Apr 1 2020 xfsslower
$ export PATH=$PATH:/usr/share/bcc/tools配置完,你就可以運⾏ cachestat 和 cachetop 命令了。⽐如,下⾯就是⼀個 cachestat 的運⾏界⾯,它以1秒的時間間隔,輸 出了3組快取統計資料:
$ cachestat 1 3 TOTAL MISSES HITS DIRTIES BUFFERS_MB CACHED_MB 2 0 2 1 17 279 2 0 2 1 17 279 2 0 2 1 17 279你可以看到,cachestat 的輸出其實是⼀個表格。每⾏代表⼀組資料,⽽每⼀列代表不同的快取統計指標。這些指標從左到右 依次表示: TOTAL , 表示總的 I/O 次數; MISSES ,表示快取未命中的次數; HITS , 表示快取命中的次數; DIRTIES, 表示新增到快取中的髒⻚數; BUFFERS_MB 表示 Buffers 的⼤⼩,以 MB 為單位; CACHED_MB 表示 Cache 的⼤⼩,以 MB 為單位。 接下來我們再來看⼀個 cachetop 的運⾏界⾯:
$ cachetop 11:58:50 Buffers MB: 258 / Cached MB: 347 / Sort: HITS / Order: ascending PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT% 13029 root python 1 0 0 100.0% 0.0%它的輸出跟 top 類似,預設按照快取的命中次數(HITS)排序,展示了每個程序的快取命中情況。具體到每⼀個指標,這⾥ 的 HITS、MISSES和DIRTIES ,跟 cachestat ⾥的含義⼀樣,分別代表間隔時間內的快取命中次數、未命中次數以及新增到 快取中的髒⻚數。 ⽽ READ_HIT 和 WRITE_HIT ,分別表示讀和寫的快取命中率。 指定⽂件的快取⼤⼩ 除了快取的命中率外,還有⼀個指標你可能也會很感興趣,那就是指定⽂件在記憶體中的快取⼤⼩。你可以使⽤ pcstat 這個⼯ 具,來檢視⽂件在記憶體中的快取⼤⼩以及快取⽐例。 pcstat 是⼀個基於 Go 語⾔開發的⼯具,所以安裝它之前,你⾸先應該安裝 Go 語⾔,你可以點選這⾥下載安裝。 安裝完 Go 語⾔,再運⾏下⾯的命令安裝 pcstat:
$ export GOPATH=~/go $ export PATH=~/go/bin:$PATH $ go get golang.org/x/sys/unix $ go get github.com/tobert/pcstat/pcstat全部安裝完成後,你就可以運⾏ pcstat 來檢視⽂件的快取情況了。⽐如,下⾯就是⼀個 pcstat 運⾏的示例,它展示了 /bin/ls 這個⽂件的快取情況:
備註:
pcstat安裝參考:https://www.cnblogs.com/Courage129/p/14282282.html
$ pcstat /bin/ls +---------+----------------+------------+-----------+---------+ | Name | Size (bytes) | Pages | Cached | Percent | |---------+----------------+------------+-----------+---------| | /bin/ls | 133792 | 33 | 0 | 000.000 | +---------+----------------+------------+-----------+---------+這個輸出中,Cached 就是 /bin/ls 在快取中的⼤⼩,⽽ Percent 則是快取的百分⽐。你看到它們都是 0,這說明 /bin/ls 並不在 快取中。 接著,如果你執⾏⼀下 ls 命令,再運⾏相同的命令來檢視的話,就會發現 /bin/ls 都在快取中了:
$ ls $ pcstat /bin/ls +---------+----------------+------------+-----------+---------+ | Name | Size (bytes) | Pages | Cached | Percent | |---------+----------------+------------+-----------+---------| | /bin/ls | 133792 | 33 | 33 | 100.000 | +---------+----------------+------------+-----------+---------+知道了快取相應的指標和檢視系統快取的⽅法後,接下來,我們就進⼊今天的正式案例。 跟前⾯的案例⼀樣,今天的案例也是基於 Ubuntu 18.04,當然同樣適⽤於其他的 Linux 系統。 機器配置:2 CPU,8GB 記憶體。 預先按照上⾯的步驟安裝 bcc 和 pcstat 軟體包,並把這些⼯具的安裝路徑新增到到 PATH 環境變數中。 預先安裝 Docker 軟體包,⽐如 apt-get install docker.io 案例⼀ 第⼀個案例,我們先來看⼀下上⼀節提到的 dd 命令。 dd 作為⼀個磁碟和⽂件的拷⻉⼯具,經常被拿來測試磁碟或者⽂件系統的讀寫效能。不過,既然快取會影響到效能,如果⽤ dd對同⼀個⽂件進⾏多次讀取測試,測試的結果會怎麼樣呢? 我們來動⼿試試。⾸先,開啟兩個終端,連線到 Ubuntu 機器上,確保 bcc 已經安裝配置成功。 然後,使⽤ dd 命令⽣成⼀個臨時⽂件,⽤於後⾯的⽂件讀取測試:
# ⽣成⼀個512MB的臨時⽂件 $ dd if=/dev/sda1 of=file bs=1M count=512 # 清理快取 $ echo 3 > /proc/sys/vm/drop_cachesdd命令也⽀持直接IO的 有選項oflflag和iflflag 所以dd也可以⽤來繞過cache buff做測試 繼續在第⼀個終端,運⾏ pcstat 命令,確認剛剛⽣成的⽂件不在快取中。如果⼀切正常,你會看到 Cached 和 Percent 都是 0:
備註:
$ pcstat file +-------+----------------+------------+-----------+---------+ | Name | Size (bytes) | Pages | Cached | Percent | |-------+----------------+------------+-----------+---------| | file | 536870912 | 131072 | 0 | 000.000 | +-------+----------------+------------+-----------+---------+還是在第⼀個終端中,現在運⾏ cachetop 命令:
# 每隔5秒重新整理⼀次資料 $ cachetop 5這次是第⼆個終端,運⾏ dd 命令測試⽂件的讀取速度:
$ dd if=file of=/dev/null bs=1M 512+0 records in 512+0 records out 536870912 bytes (537 MB, 512 MiB) copied, 16.0509 s, 33.4 MB/s從 dd 的結果可以看出,這個⽂件的讀效能是 33.4 MB/s。由於在 dd 命令運⾏前我們已經清理了快取,所以 dd 命令讀取資料 時,肯定要通過⽂件系統從磁碟中讀取。 不過,這是不是意味著, dd 所有的讀請求都能直接傳送到磁碟呢? 我們再回到第⼀個終端, 檢視 cachetop 界⾯的快取命中情況:
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT% \.\.\.從 cachetop 的結果可以發現,並不是所有的讀都落到了磁碟上,事實上讀請求的快取命中率只有 50% 。 接下來,我們繼續嘗試相同的測試命令。先切換到第⼆個終端,再次執⾏剛才的 dd 命令:
3264 root dd 37077 37330 0 49.8% 50.2%
$ dd if=file of=/dev/null bs=1M 512+0 records in 512+0 records out 536870912 bytes (537 MB, 512 MiB) copied, 0.118415 s, 4.5 GB/s看到這次的結果,有沒有點⼩驚訝?磁碟的讀效能居然變成了 4.5 GB/s,⽐第⼀次的結果明顯⾼了太多。為什麼這次的結果 這麼好呢? 不妨再回到第⼀個終端,看看 cachetop 的情況:
10:45:22 Buffers MB: 4 / Cached MB: 719 / Sort: HITS / Order: ascending PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT% \.\.\. 32642 root dd 131637 0 0 100.0% 0.0%顯然,cachetop也有了不⼩的變化。你可以發現,這次的讀的快取命中率是100.0%,也就是說這次的 dd 命令全部命中了緩 存,所以才會看到那麼⾼的效能。 然後,回到第⼆個終端,再次執⾏ pcstat 檢視⽂件 fifile 的快取情況:
$ pcstat file +-------+----------------+------------+-----------+---------+ | Name | Size (bytes) | Pages | Cached | Percent | |-------+----------------+------------+-----------+---------| | file | 536870912 | 131072 | 131072 | 100.000 | +-------+----------------+------------+-----------+---------+從 pcstat 的結果你可以發現,測試⽂件 fifile 已經被全部快取了起來,這跟剛才觀察到的快取命中率 100% 是⼀致的。 這兩次結果說明,系統快取對第⼆次 dd 操作有明顯的加速效果,可以⼤⼤提⾼⽂件讀取的效能。 但同時也要注意,如果我們把 dd 當成測試⽂件系統性能的⼯具,由於快取的存在,就會導致測試結果嚴重失真。 案例⼆ 接下來,我們再來看⼀個⽂件讀寫的案例。這個案例類似於前⾯學過的不可中斷狀態程序的例⼦。它的基本功能⽐較簡單,也 就是每秒從磁碟分割槽 /dev/sda1 中讀取 32MB 的資料,並打印出讀取資料花費的時間。 為了⽅便你運⾏案例,我把它打包成了⼀個 Docker 映象。 跟前⾯案例類似,我提供了下⾯兩個選項,你可以根據系統配置, ⾃⾏調整磁碟分割槽的路徑以及 I/O 的⼤⼩。 -d 選項,設定要讀取的磁碟或分割槽路徑,預設是查詢字首為 /dev/sd 或者 /dev/xvd 的磁碟。 -s 選項,設定每次讀取的資料量⼤⼩,單位為位元組,預設為 33554432(也就是 32MB)。 這個案例同樣需要你開啟兩個終端。分別 SSH 登入到機器上後,先在第⼀個終端中運⾏ cachetop 命令:
# 每隔5秒重新整理⼀次資料 $ cachetop 5接著,再到第⼆個終端,執⾏下⾯的命令運⾏案例:
$ docker run --privileged --name=app -itd feisky/app:io-direct案例運⾏後,我們還需要運⾏下⾯這個命令,來確認案例已經正常啟動。如果⼀切正常,你應該可以看到類似下⾯的輸出:
$ docker logs app Reading data from disk /dev/sdb1 with buffer size 33554432 Time used: 0.929935 s to read 33554432 bytes Time used: 0.949625 s to read 33554432 bytes從這⾥你可以看到,每讀取 32 MB 的資料,就需要花 0.9 秒。這個時間合理嗎?我想你第⼀反應就是,太慢了吧。那這是不 是沒⽤系統快取導致的呢? 我們再來檢查⼀下。回到第⼀個終端,先看看 cachetop 的輸出,在這⾥,我們找到案例程序 app 的快取使⽤情況:
16:39:18 Buffers MB: 73 / Cached MB: 281 / Sort: HITS / Order: ascending PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT% 21881 root app 1024 0 0 100.0% 0.0%這個輸出似乎有點意思了。1024 次快取全部命中,讀的命中率是 100%,看起來全部的讀請求都經過了系統快取。但是問題 ⼜來了,如果真的都是快取 I/O,讀取速度不應該這麼慢。 不過,話說回來,我們似乎忽略了另⼀個重要因素,每秒實際讀取的資料⼤⼩。HITS 代表快取的命中次數,那麼每次命中能 讀取多少資料呢?⾃然是⼀⻚。 前⾯講過,記憶體以⻚為單位進⾏管理,⽽每個⻚的⼤⼩是 4KB。所以,在5秒的時間間隔⾥,命中的快取為 1024*4K/1024 = 4MB,再除以5 秒,可以得到每秒讀的快取是 0.8MB,顯然跟案例應⽤的32 MB/s 相差太多。 ⾄於為什麼只能看到 0.8 MB 的 HITS,我們後⾯再解釋,這⾥你先知道怎麼根據結果來分析就可以了。 這也進⼀步驗證了我們的猜想,這個案例估計沒有充分利⽤系統快取。其實前⾯我們遇到過類似的問題,如果為系統調⽤設定 直接 I/O 的標誌,就可以繞過系統快取。 那麼,要判斷應⽤程式是否⽤了直接I/O,最簡單的⽅法當然是觀察它的系統調⽤,查詢應⽤程式在調⽤它們時的選項。使⽤ 什麼⼯具來觀察系統調⽤呢?⾃然還是 strace。 繼續在終端⼆中運⾏下⾯的 strace 命令,觀察案例應⽤的系統調⽤情況。注意,這⾥使⽤了 pgrep 命令來查詢案例程序的 PID 號:
# strace -p $(pgrep app) strace: Process 4988 attached restart_syscall(<\.\.\. resuming interrupted nanosleep \.\.\.>) = 0 openat(AT_FDCWD, "/dev/sdb1", O_RDONLY|O_DIRECT) = 4 mmap(NULL, 33558528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f448d240000 read(4, "8vq\213\314\264u\373\4\336K\224\25@\371\1\252\2\262\252q\221\n0\30\225bD\252\266@J"\.\.\., 33554432) = 33554432 write(1, "Time used: 0.948897 s to read 33"\.\.\., 45) = 45 close(4)從 strace 的結果可以看到,案例應⽤調⽤了 openat 來開啟磁碟分割槽 /dev/sdb1,並且傳⼊的引數為 O_RDONLY|O_DIRECT(中間的豎線表示或)。 O_RDONLY 表示以只讀⽅式開啟,⽽ O_DIRECT 則表示以直接讀取的⽅式開啟,這會繞過系統的快取。 驗證了這⼀點,就很容易理解為什麼讀 32 MB的資料就都要那麼久了。直接從磁碟讀寫的速度,⾃然遠慢於對快取的讀寫。 這也是快取存在的最⼤意義了。 找出問題後,我們還可以在再看看案例應⽤的原始碼,再次驗證⼀下:
int flags = O_RDONLY | O_LARGEFILE | O_DIRECT; int fd = open(disk, flags, 0755);上⾯的程式碼,很清楚地告訴我們:它果然⽤了直接 I/O。 找出了磁碟讀取緩慢的原因,優化磁碟讀的效能⾃然不在話下。修改原始碼,刪除 O_DIRECT 選項,讓應⽤程式使⽤快取 I/O ,⽽不是直接 I/O,就可以加速磁碟讀取速度。 app-cached.c 就是修復後的原始碼,我也把它打包成了⼀個容器映象。在第⼆個終端中,按 Ctrl+C 停⽌剛才的 strace 命令,運 ⾏下⾯的命令,你就可以啟動它:
# 刪除上述案例應⽤ $ docker rm -f app # 運⾏修復後的應⽤ $ docker run --privileged --name=app -itd feisky/app:io-cached還是第⼆個終端,再來運⾏下⾯的命令檢視新應⽤的⽇志,你應該能看到下⾯這個輸出:
$ docker logs app Reading data from disk /dev/sdb1 with buffer size 33554432 Time used: 0.037342 s s to read 33554432 bytes Time used: 0.029676 s to read 33554432 bytes現在,每次只需要 0.03秒,就可以讀取 32MB 資料,明顯⽐之前的 0.9 秒快多了。所以,這次應該⽤了系統快取。 我們再回到第⼀個終端,檢視 cachetop 的輸出來確認⼀下:
16:40:08 Buffers MB: 73 / Cached MB: 281 / Sort: HITS / Order: ascending PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT% 22106 root app 40960 0 0 100.0% 0.0%果然,讀的命中率還是 100%,HITS (即命中數)卻變成了 40960,同樣的⽅法計算⼀下,換算成每秒位元組數正好是 32 MB(即 40960*4k/5/1024=32M)。 這個案例說明,在進⾏ I/O 操作時,充分利⽤系統快取可以極⼤地提升效能。 但在觀察快取命中率時,還要注意結合應⽤程 序實際的 I/O ⼤⼩,綜合分析快取的使⽤情況。 案例的最後,再回到開始的問題,為什麼優化前,通過 cachetop 只能看到很少⼀部分資料的全部命中,⽽沒有觀察到⼤量數 據的未命中情況呢?這是因為,cachetop ⼯具並不把直接 I/O 算進來。這也⼜⼀次說明了,瞭解⼯具原理的重要。 cachetop 的計算⽅法涉及到 I/O 的原理以及⼀些核心的知識,如果你想了解它的原理的話,可以點選這⾥檢視它的源代 碼 總結 Buffers 和 Cache 可以極⼤提升系統的 I/O 效能。通常,我們⽤快取命中率,來衡量快取的使⽤效率。命中率越⾼,表示快取 被利⽤得越充分,應⽤程式的效能也就越好。 你可以⽤ cachestat 和 cachetop 這兩個⼯具,觀察系統和程序的快取命中情況。其中, cachestat 提供了整個系統快取的讀寫命中情況。 cachetop 提供了每個程序的快取命中情況。 不過要注意,Buffers 和 Cache 都是作業系統來管理的,應⽤程式並不能直接控制這些快取的內容和⽣命週期。所以,在應⽤ 程式開發中,⼀般要⽤專⻔的快取元件,來進⼀步提升效能。 ⽐如,程式內部可以使⽤堆或者棧明確宣告記憶體空間,來儲存需要快取的資料。再或者,使⽤ Redis 這類外部快取服務,優 化資料的訪問效率。 思考 最後,我想給你留下⼀道思考題,幫你更進⼀步瞭解快取的原理。 今天的第⼆個案例你應該很眼熟,因為前⾯不可中斷程序的⽂章⽤的也是直接I/O的例⼦,不過那次,我們是從CPU使⽤率和 程序狀態的⻆度來分析的。對⽐CPU和快取這兩個不同⻆度的分析思路,你有什麼樣的發現呢? 專注事業!