要實踐低延遲的黃金法則,我們不能瞎子摸象。現代 CPU 與 Linux 核心在執行時會觸發無數的 System Events ,例如 cache misses 或 branch misprediction,而監聽這些事件的最佳武器就是 perf-events。它是 Linux 內建的效能分析工具,用於分析程式執行期間的 CPU 行為與延遲來源。其能讓我們直接讀取 CPU 內部的硬體效能監控單元,將抽象的延遲具體化為數據,可觀察項目包含:cycles、instructions、branch、cache、stall 等 PMU 事件。常用指令如下:
1. perf list —— 條列目前 CPU 支援的 PMU 事件
在開始監控前,我們必須先知道當前硬體提供了哪些探針。perf list 能列出當前 CPU 架構與 Linux 核心支援的所有硬體、軟體與微架構事件。
perf list
perf list | grep cache
perf list | grep stalled
# 分支預測類 → 解救 Pipeline
perf list | grep -iE 'branch|pred'
# 快取與記憶體頁表類 → 解救 Memory Stall
perf list | grep -iE 'L1-|tlb'
# 微架構效率類 → 計算 IPC
perf list | grep -iE 'cycle|instruction'
# 核心排程類 → 作業系統造成的延遲抖動
perf list | grep -iE 'context|page-fault'
2. perf stat —— 蒐集整體事件的統計總覽
perf stat 可用來判斷 bottleneck 的類型:frontend、backend、branch、memory。
現代 Linux perf 還內建了由 Intel 提出的 Top-Down 微架構分析模型 (TMA),不加任何參數直接執行 perf stat 可自動利用硬體計數器將 CPU 週期分類,直接印出這四個維度的百分比。
# 執行基礎統計
perf stat ./low_latency_app
# 顯示較完整統計
perf stat -d ./low_latency_app args
# 重複執行 5 次並計算平均數據與統計誤差
perf stat -r 5 ./low_latency_app
# 指定要觀察的 event
perf stat -e stalled-cycles-frontend,stalled-cycles-backend ./low_latency_app
# 一口氣觀測低延遲最在意的 6 大指標
perf stat -e cycles,instructions,branch-misses,L1-dcache-load-misses,L1-icache-load-misses,context-switches ./low_latency_app
3. perf record —— 取樣並產生 perf.data
perf record 用於蒐集執行過程的效能數據,並寫入預設為 perf.data 的檔案。其常搭配的 options 類型如下。
呼叫堆疊追蹤
g:開啟 call graph,追蹤是哪一個上游函式呼叫了這個慢速函式。-call-graph fp:CPU 只需要讀取 %rbp 暫存器就能完成堆疊回溯,極低 overhead。前提是編譯 C++ 程式碼時絕對不能開啟 -fomit-frame-pointer 優化旗標,否則無法建立正確的堆疊。-call-graph dwarf:透過執行檔中的 DWARF 偵錯資訊成功回溯堆疊,且能完美處理 C++ 內聯函式。數據最完整但 overhead 過高。採樣頻率控管
F N:固定頻率取樣,設定每秒固定採樣 N 次。c N:每發生 N 次指定事件,就觸發一次採樣。環境與檔案配置
o file_name:指定輸出的檔案名稱。e event:指定要追蹤的特定事件,如 cycles、branch-misses 或自定義的 probe。這些參數可平衡「採樣完整度」與「對系統造成的額外開銷」,通常會建議開啟 -g --call-graph fp。
# 固定頻率取樣
perf record -F 999 -g --call-graph fp ./low_latency_app args -o perf.data
# 針對 cycles 特定事件取樣
perf record -e cycles:u -c 100000 -g --call-graph fp ./low_latency_app args
perf record -e cycles --call-graph fp -F 99 -o perf.data ./low_latency_app
# 針對 frontend stall 取樣
perf record -e stalled-cycles-frontend:u -c 100000 -g ./low_latency_app args
4. perf report —— 以互動式介面查看及分析
perf report 可讀取 perf.data ,並進行 Self % 及 Children % 的互動式分析。Self % 表示該函式本身耗時比例;Children % 則代表該函式及其子呼叫總耗時比例。
若想找出 CPU 週期都花在哪裡:
perf record -g ./low_latency_app
perf report
perf report -T # 呈現樹狀 report
5. perf diff —— 比較兩個 perf.data
Linux perf diff 的預設邏輯是:後面檔案相對於前面檔案的百分比變化,也就是 Delta。若加上 -m 或是完整參數 --period,就能直接攤開硬體事件發生的絕對次數,讓使用者知道程式到底實質上少執行了多少次運算。
perf diff old.data new.data
# perf diff [基準檔案] [對比檔案]
# 同時顯示事件絕對次數與百分比的對比
perf diff -m old.data new.data # Raw Event Counts / Period
6. perf script —— 將 perf.data 轉為文字格式
perf script 可將 perf.data 轉為文字格式,供進階分析或 FlameGraph 火焰圖使用。
perf script -i perf.data
# 一鍵產生火焰圖
perf script | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > flamegraph.svg
此外還有一個進階用法 —— regex 大量篩選:
flamegraph.pl --focus "regex" --ignore "regex" out.folded > flame.svg
--focus "regex":只將符合條件的 Symbol 及其子呼叫高亮呈現或放大顯示,非常適合拿來追蹤特定模組的效能。
--ignore "regex":直接從圖中剔除不感興趣的函數,例如某些背景執行緒、特定系統呼叫,讓視覺更乾淨。
編譯參數 —— -O2 -g -fno-omit-frame-pointer:在 -O2 優化下,編譯器預設會把 Frame Pointer 省略掉來騰出暫存器,這會導致 perf 抓不到完整的呼叫堆疊。加上這個參數可以強制保留,火焰圖才不會變成斷層。
錄製參數 —— -g --callgraph -p:如果在特定舊核心或遇到 Frame pointer 依然斷掉的情況,可以明確指定採樣方式,例如改用 DWARF。
7. perf probe —— 動態插入自定義事件點
在指定函式插入 probe 是一種自定義的 event,常用於函式 entry/exit,例如交易系統中的 on_tick 或 send_order。
在 Low-Latency C++ 的開發中,perf probe 是非常高級的黑客技巧。當我們不想重新修改程式碼及編譯,又想精準抓取某個關鍵函式的進出延遲時,這就是標準作法。
# 新增 entry probe
perf probe -x ./low_latency_app NewFunc
# 新增 return probe
perf probe -x ./low_latency_app 'NewFunc%return'
# record 同時記錄 probe
perf record -e probe_low_latency_app:NewFunc -g ./low_latency_app
# 開啟 -g 抓取調用棧
perf record -e probe_low_latency_app:NewFunc,probe_low_latency_app:NewFunc_return -g ./low_latency_app