磁盤通常是計算機最慢的子系統,也是最容易出現性能瓶頸的地方,因為磁盤離 CPU 距離最遠而且 CPU 通路磁盤要涉及到機械操作,比如轉軸、尋軌等。通路硬碟和通路記憶體之間的速度差别是以數量級來計算的,就像1天和1分鐘的差别一樣。要監測 IO 性能,有必要了解一下基本原理和 Linux 是如何處理硬碟和記憶體之間的 IO 的。
記憶體頁
上一篇 Linux 性能監測:Memory 提到了記憶體和硬碟之間的 IO 是以頁為機關來進行的,在 Linux 系統上1頁的大小為 4K。可以用以下指令檢視系統預設的頁面大小:
$ /usr/bin/time -v date ... Page size (bytes): 4096 ...
缺頁中斷
Linux 利用虛拟記憶體極大的擴充了程式位址空間,使得原來實體記憶體不能容下的程式也可以通過記憶體和硬碟之間的不斷交換(把暫時不用的記憶體頁交換到硬碟,把需要的記憶體頁從硬碟讀到記憶體)來赢得更多的記憶體,看起來就像實體記憶體被擴大了一樣。事實上這個過程對程式是完全透明的,程式完全不用理會自己哪一部分、什麼時候被交換進記憶體,一切都有核心的虛拟記憶體管理來完成。當程式啟動的時候,Linux 核心首先檢查 CPU 的緩存和實體記憶體,如果資料已經在記憶體裡就忽略,如果資料不在記憶體裡就引起一個缺頁中斷(Page Fault),然後從硬碟讀取缺頁,并把缺頁緩存到實體記憶體裡。缺頁中斷可分為主缺頁中斷(Major Page Fault)和次缺頁中斷(Minor Page Fault),要從磁盤讀取資料而産生的中斷是主缺頁中斷;資料已經被讀入記憶體并被緩存起來,從記憶體緩存區中而不是直接從硬碟中讀取資料而産生的中斷是次缺頁中斷。
上面的記憶體緩存區起到了預讀硬碟的作用,核心先在實體記憶體裡尋找缺頁,沒有的話産生次缺頁中斷從記憶體緩存裡找,如果還沒有發現的話就從硬碟讀取。很顯然,把多餘的記憶體拿出來做成記憶體緩存區提高了通路速度,這裡還有一個命中率的問題,運氣好的話如果每次缺頁都能從記憶體緩存區讀取的話将會極大提高性能。要提高命中率的一個簡單方法就是增大記憶體緩存區面積,緩存區越大預存的頁面就越多,命中率也會越高。下面的 time 指令可以用來檢視某程式第一次啟動的時候産生了多少主缺頁中斷和次缺頁中斷:
$ /usr/bin/time -v date ... Major (requiring I/O) page faults: 1 Minor (reclaiming a frame) page faults: 260 ...
File Buffer Cache
從上面的記憶體緩存區(也叫檔案緩存區 File Buffer Cache)讀取頁比從硬碟讀取頁要快得多,是以 Linux 核心希望能盡可能産生次缺頁中斷(從檔案緩存區讀),并且能盡可能避免主缺頁中斷(從硬碟讀),這樣随着次缺頁中斷的增多,檔案緩存區也逐漸增大,直到系統隻有少量可用實體記憶體的時候 Linux 才開始釋放一些不用的頁。我們運作 Linux 一段時間後會發現雖然系統上運作的程式不多,但是可用記憶體總是很少,這樣給大家造成了 Linux 對記憶體管理很低效的假象,事實上 Linux 把那些暫時不用的實體記憶體高效的利用起來做預存(記憶體緩存區)呢。下面列印的是 VPSee 的一台 Sun 伺服器上的實體記憶體和檔案緩存區的情況:
$ cat /proc/meminfo MemTotal: 8182776 kB MemFree: 3053808 kB Buffers: 342704 kB Cached: 3972748 kB
這台伺服器總共有 8GB 實體記憶體(MemTotal),3GB 左右可用記憶體(MemFree),343MB 左右用來做磁盤緩存(Buffers),4GB 左右用來做檔案緩存區(Cached),可見 Linux 真的用了很多實體記憶體做 Cache,而且這個緩存區還可以不斷增長。
頁面類型
Linux 中記憶體頁面有三種類型:
- Read pages,隻讀頁(或代碼頁),那些通過主缺頁中斷從硬碟讀取的頁面,包括不能修改的靜态檔案、可執行檔案、庫檔案等。當核心需要它們的時候把它們讀到記憶體中,當記憶體不足的時候,核心就釋放它們到空閑清單,當程式再次需要它們的時候需要通過缺頁中斷再次讀到記憶體。
- Dirty pages,髒頁,指那些在記憶體中被修改過的資料頁,比如文本檔案等。這些檔案由 pdflush 負責同步到硬碟,記憶體不足的時候由 kswapd 和 pdflush 把資料寫回硬碟并釋放記憶體。
- Anonymous pages,匿名頁,那些屬于某個程序但是又和任何檔案無關聯,不能被同步到硬碟上,記憶體不足的時候由 kswapd 負責将它們寫到交換分區并釋放記憶體。
IO’s Per Second(IOPS)
每次磁盤 IO 請求都需要一定的時間,和通路記憶體比起來這個等待時間簡直難以忍受。在一台 2001 年的典型 1GHz PC 上,磁盤随機通路一個 word 需要 8,000,000 nanosec = 8 millisec,順序通路一個 word 需要 200 nanosec;而從記憶體通路一個 word 隻需要 10 nanosec.(資料來自:Teach Yourself Programming in Ten Years)這個硬碟可以提供 125 次 IOPS(1000 ms / 8 ms)。
順序 IO 和 随機 IO
IO 可分為順序 IO 和 随機 IO 兩種,性能監測前需要弄清楚系統偏向順序 IO 的應用還是随機 IO 應用。順序 IO 是指同時順序請求大量資料,比如資料庫執行大量的查詢、流媒體服務等,順序 IO 可以同時很快的移動大量資料。可以這樣來評估 IOPS 的性能,用每秒讀寫 IO 位元組數除以每秒讀寫 IOPS 數,rkB/s 除以 r/s,wkB/s 除以 w/s. 下面顯示的是連續2秒的 IO 情況,可見每次 IO 寫的資料是增加的(45060.00 / 99.00 = 455.15 KB per IO,54272.00 / 112.00 = 484.57 KB per IO)。相對随機 IO 而言,順序 IO 更應該重視每次 IO 的吞吐能力(KB per IO):
$ iostat -kx 1 avg-cpu: %user %nice %system %iowait %steal %idle 0.00 0.00 2.50 25.25 0.00 72.25 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util sdb 24.00 19995.00 29.00 99.00 4228.00 45060.00 770.12 45.01 539.65 7.80 99.80 avg-cpu: %user %nice %system %iowait %steal %idle 0.00 0.00 1.00 30.67 0.00 68.33 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util sdb 3.00 12235.00 3.00 112.00 768.00 54272.00 957.22 144.85 576.44 8.70 100.10
$ iostat -kx 1 avg-cpu: %user %nice %system %iowait %steal %idle 1.75 0.00 0.75 0.25 0.00 97.26 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util sdb 0.00 52.00 0.00 57.00 0.00 436.00 15.30 0.03 0.54 0.23 1.30 avg-cpu: %user %nice %system %iowait %steal %idle 1.75 0.00 0.75 0.25 0.00 97.24 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util sdb 0.00 56.44 0.00 66.34 0.00 491.09 14.81 0.04 0.54 0.19 1.29
SWAP
$ cat /proc/meminfo MemTotal: 8182776 kB MemFree: 2125476 kB Buffers: 347952 kB Cached: 4892024 kB SwapCached: 112 kB ... SwapTotal: 4096564 kB SwapFree: 4096424 kB ... $ vmstat 1 procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------ r b swpd free buff cache si so bi bo in cs us sy id wa st 1 2 260008 2188 144 6824 11824 2584 12664 2584 1347 1174 14 0 0 86 0 2 1 262140 2964 128 5852 24912 17304 24952 17304 4737 2341 86 10 0 0 4