效能分析(7)- 未利用系統快取導致 I/O 緩慢案例

小菠蘿測試筆記發表於2020-08-17

效能分析小案例系列,可以通過下面連結檢視哦

https://www.cnblogs.com/poloyy/category/1814570.html

 

前提

前面有學到 Buffer 和 Cache 的概念:https://www.cnblogs.com/poloyy/p/13503848.html

我們來簡單複習下

 

Buffer 和 Cache 的設計目的

為了提升系統的 I/O 效能,它們利用記憶體,充當起慢速磁碟和快速 CPU 之間的橋樑,可以加速 I/O 的訪問速度

 

Buffer 和 Cache

  • BUffer:對磁碟的讀寫資料快取
  • Cache:對檔案系統的讀寫資料快取

 

讀寫角度

  • 的角度來說,不僅可以優化磁碟和檔案的寫入,對應用程式也有好處,應用程式可以在資料真正落盤前,就返回去做其他工作
  • 的角度來說,不僅可以提高那些頻繁訪問資料的讀取速度,也可以降低頻繁 I/O 對磁碟的壓力

 

引入主題

既然 Buffer 和 Cache 對系統效能有很大影響,那我們在軟體開發的過程中,能不能利用這 一點,來優化 I/O 效能,提升應用程式的執行效率呢? 答案自然是肯定的

 

快取命中率

靈魂拷問

我們想利用快取來提升程式的執行效率,應該怎麼評估這個效果呢?換句話說,有沒有哪個指標可以衡量快取使用的好壞呢?

 

靈魂回答

  • 快取命中率
  • 指直接通過快取獲取資料的請求次數,佔所有資料請求次數的百分比【使用快取請求次數 / 總請求次數】
  • 命中率越高,表示使用快取帶來的收益越高,應用程式的效能也就越好

 

快取的重要性

  • 快取是現在所有高併發系統必需的核心模組
  • 主要作用:把經常訪問的資料(熱點資料),提前讀入到記憶體中,下次訪問時就可以直接從記憶體讀取資料,而不需要經過硬碟,從而加快應用程式的響應速度

 

cachestat、cachetop

獨立的快取模組通常會提供查詢介面,方便我們隨時檢視快取的命中情況

不過 Linux 系統中並沒有直接提供這些介面,所以這裡要介紹一下,cachestat 和 cachetop ,它們正是檢視系統快取命中情況的工具

  • cachestat 提供了整個作業系統快取的讀寫命中情況
  • cachetop 提供了每個程式的快取命中情況

 

Ubuntu 安裝工具

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)

 

配置環境變數

vim /etc/profile

# 在檔案結尾處新增
export PATH=$PATH:/usr/share/bcc/tools

# 儲存檔案後
source /etc/profile

 

cachestat

# 它以 1 秒的時間間隔,輸出了 3 組快取統計資料
cachestat 1 3

欄位說明

 

cachetop

cachetop

  • 預設按照快取的命中次數(HITS)排序,展示了每個程式的快取命中情況
  • 具體到每一個指標,這裡的 HITS、MISSES 和 DIRTIES ,跟 cachestat 裡的含義 一樣,分別代表間隔時間內的快取命中次數、未命中次數以及新增到快取中的髒頁數
  • READ_HIT:讀的快取命中率
  • WRITE_HIT:寫的快取命中率

 

檢視檔案的快取大小

可以使用 pcstat 這個工具,來檢視檔案在記憶體中的快取大小以及快取比例

 

安裝 pcstat

# 安裝 go
apt install golang-go

vim /etc/profile

# 在檔案結尾處新增
export GOPATH=~/go
export PATH=~/go/bin:$PATH

# 儲存檔案後
source /etc/profile
 
go get golang.org/x/sys/unix
go get github.com/tobert/pcstat/pcstat

 

執行 pcstat

pcstat /bin/ls

Cached 就是 /bin/ls 在快取中的大小,而 Percent 則是快取的百分比,看到它們都是 0,這說明 /bin/ls 並不在快取中

 

執行 ls 命令,再來檢視

ls
pcstat /bin/ls

發現都在快取中了

 

講案例前的準備

  • 系統:Ubuntu 18.04,當然同樣適用於其他的 Linux 系 統。
  • 機器配置:2 CPU,2 GB 記憶體
  • 預先按照上面的步驟安裝 bcc 和 pcstat 軟體包,並把這些工具的安裝路徑新增到到 PATH 環境變數中
  • 預先安裝 Docker 軟體包: apt-get install docker.io 
  • 開啟兩個終端同時連到 Linux 上

 

案例一:通過 dd 寫入讀取檔案

注意:沒說第幾個終端都是預設第一個終端執行命令哦

 

 dd 命令生成一個臨時檔案

# 生成一個 512MB 的臨時檔案
dd if=/dev/sda1 of=file bs=1M count=512

# 清理快取
echo 3 > /proc/sys/vm/drop_caches

 

執行 pcstat 檢視 file 檔案

pcstat file

確認剛剛生成的檔案不在快取中。如果一切正常, 會看到 Cached 和 Percent 都是 0

 

執行 cachetop 

# 每隔 1 秒重新整理一次資料
cachetop 1

 

第二個終端執行 dd 命令

dd if=file of=/dev/null bs=1M

  • 從 dd 的結果可以看出,這個檔案的讀效能是 639 MB/s
  • 由於在 dd 命令執行前我們已經清理了快取,所以 dd 命令讀取資料時,肯定要通過檔案系統從磁碟中讀取

 

檢視 cachetop

可以看到 dd 命令並不是所有的讀都落到了磁碟上,讀請求的快取命中率只有 50%

 

第二個終端再次執行 dd 命令

dd if=file of=/dev/null bs=1M

磁碟的讀效能蹭蹭蹭往上漲,去到了 1.6GB/s

 

再檢視 cachetop

可以發現,這次讀的快取命中率是 100%

 

第二個終端執行 pcstat

pcstat  file

測試檔案已經被全部快取起來了,和剛剛 cachetop 觀察到快取命中率 100% 是一致的

 

總結

這兩次結果說明,系統快取對第二次 dd 操作有明顯的加速效果,可以大大提高檔案讀取的效能。

 

案例二

前提

  • 這裡執行了一個不可中斷狀態的程式
  • 功能:是每秒從磁碟分割槽 /dev/sda1 中讀取 32MB 的資料, 並列印出讀取資料花費的時間

 

檢視應用日誌

從這裡可以看到,每讀取 32 MB 的資料,就需要花 0.9 秒

 

靈魂拷問

這個時間合理嗎?這也太慢了吧,那這是不是沒用系統快取導致的呢?

 

檢視 cachetop

結果分析

讀的命中率雖然是 100%,命中次數是 1024,看起來全部的讀請求都經過了系統快取

 

靈魂又拷問了

全都是快取 I/O,讀取速度不應該這麼慢

 

深入分析

  • 另一個重要因素,每秒實際讀取的資料大小,HITS 代表快取的命中次數,那麼每次命中能讀取多少資料呢?自然是一頁
  • 記憶體以頁為單位進行管理,而每個頁的大小是 4KB
  • 所以,在 5 秒的時間間隔 裡,命中的快取為 1024*4K/1024 = 4MB,再除以 5 秒,可以得到每秒讀的快取是 0.8MB,顯然跟案例應用的 32 MB/s 相差太多

 

結果猜想

這個案例估計沒有充分利用系統快取,如果系統呼叫設定直接 I/O 的標誌,就可以繞過系統快取

 

通過 strace 觀察系統呼叫

strace -p $(pgrep app)

結果分析

  • 從 strace 的結果可以看到,案例應用呼叫了 openat 來開啟磁碟分割槽 /dev/sdb1,並且傳 入的引數為 O_RDONLY|O_DIRECT(中間的豎線表示或)
  • O_RDONLY 表示以只讀方式開啟
  • 而 O_DIRECT 則表示以直接讀取的方式開啟,這會繞過系統的快取
  • 驗證了這一點,就很容易理解為什麼讀 32 MB 的資料就都要那麼久了
  • 直接從磁碟讀寫的速度,自然遠慢於對快取的讀寫,這也是快取存在的最大意義了

 

檢視應用原始碼

它果然用了直接 I/O

 

修改原始碼

刪除 O_DIRECT 選項,讓應用程式使用快取 I/O ,而不是直接 I/O,就可以加速磁碟讀取速度

 

驗證修復後的應用

檢視新應用的日誌

結果分析

現在,每次只需要 0.03 秒,就可以讀取 32MB 資料,明顯比之前的 0.9 秒快多了。所以,這次應該用了系統快取

 

再次檢視 cachetop

結果分析

  • 果然,讀的命中率還是 100%,HITS (即命中數)卻變成了 40960
  • 同樣的方法計算一 下,換算成每秒位元組數正好是 32 MB(即 40960*4k/5/1024=32M)

 

總結

  • 在進行 I/O 操作時,充分利用系統快取可以極大地提升效能。
  • 但在觀察快取命中率時,還要注意結合應用程式實際的 I/O 大小,綜合分析快取的使用情況

 

擴充套件

為什麼優化前,通過 cachetop 只能看到很少一部分資料的全部命中,而沒有觀察到大量資料的未命中情況呢?

 

回答

是因為,cachetop 工具並不把直接 I/O 算進來

 

相關文章