Linux內核追蹤神器:perf實現原理剖析
在 Linux 內核的廣袤天地里,性能優化與故障排查宛如兩座高聳的山峰,橫亙在開發者前行的道路上。為了攀登這兩座高峰,開發者們不斷尋覓著強大的工具。而 perf,無疑是其中最為耀眼的 “神器” 之一。當你在運行一個復雜的 Linux 系統時,是否常常被一些莫名的性能問題所困擾?系統突然變得卡頓,應用程序響應遲緩,可你卻無從下手,不知問題究竟出在哪里。或許是某個內核函數在不經意間消耗了大量資源,又或許是進程間的資源競爭引發了瓶頸。
在過去,面對這些問題,開發者們猶如在黑暗中摸索,只能憑借經驗和猜測來進行調試。但 perf 的出現,徹底改變了這一局面。它就像一把精準的手術刀,能夠深入到內核的每一個角落,精準地剖析系統性能。從處理器的指令周期,到緩存的命中率;從函數的調用頻率,到進程的上下文切換,perf 都能提供詳盡的數據。接下來,讓我們一同深入 perf 的世界,探尋它究竟是如何實現這一強大功能的,看看它是怎樣在復雜的內核環境中,為我們揭示性能奧秘,助力解決棘手的問題 。
一、Perf工具簡介
1.1什么是 Perf
在 Linux 性能分析的廣袤領域中,Perf 猶如一顆璀璨的明星,散發著獨特的光芒。Perf,即 Performance 的縮寫,是一款集成于 Linux 內核的性能分析工具,如同一位技藝精湛的診斷大師,能深入系統的各個角落,精準地剖析系統性能。它以事件驅動為核心機制,如同精密的儀器,能夠捕捉到硬件、軟件以及內核層面的各種性能事件。
隨著 Linux 內核的不斷演進,Perf 也在持續發展壯大。從最初簡單的性能監測,到如今具備豐富多樣的功能,Perf 已經成為 Linux 性能分析不可或缺的工具。它不僅支持硬件事件的監測,如 CPU 時鐘周期、緩存命中情況等,這些硬件事件猶如系統運行的脈搏,反映著硬件的工作狀態;還能捕捉軟件事件,如進程切換、缺頁中斷等,這些軟件事件則是系統運行的 “軟指標”,揭示著軟件層面的運行效率。此外,內核追蹤點事件、用戶程序靜態追蹤點以及動態追蹤等功能,更是讓 Perf 的監測能力如虎添翼,能夠全方位、深層次地洞察系統性能。
1.2Perf 的強大功能
Perf 之所以備受贊譽,是因為它擁有一系列強大而實用的功能,這些功能猶如一把把精準的手術刀,能夠深入剖析系統性能的各個方面。Perf是內置于Linux內核源碼樹中的性能剖析(profiling)工具。它基于事件采樣原理,以性能事件為基礎,支持針對處理器相關性能指標與操作系統相關性能指標的性能剖析,常用于性能瓶頸的查找與熱點代碼的定位。
(1)輕量級事件采樣
Perf 具備輕量級事件采樣的能力,這使其能夠通過硬件性能計數對處理器事件進行采樣,從而獲取應用程序或內核的性能數據 。它可以監測指令執行的數量,了解程序在運行過程中到底執行了多少條指令,這就像是記錄一場比賽中運動員的動作次數,能直觀反映出程序的工作量。緩存命中率也是它的監測范圍,緩存作為計算機系統中速度較快的存儲區域,緩存命中率的高低直接影響著系統的性能,Perf 能夠精確地捕捉到緩存命中的次數以及未命中的次數,讓我們清楚地知道緩存的工作效率。分支預測同樣逃不過 Perf 的 “眼睛”,分支預測是處理器為了提高執行效率而采用的一種技術,Perf 可以監測分支預測的成功率,幫助我們評估處理器在這方面的性能表現。這些監測指標就像一個個關鍵的信號,為我們揭示了系統性能的奧秘。
(2)Trace 功能
Trace 功能是 Perf 的又一強大武器,它可以跟蹤進程或內核的函數調用鏈,如同一位經驗豐富的偵探,能夠順著線索找出代碼的執行路徑。通過這一功能,Perf 可以生成函數調用圖,函數調用圖就像是一幅詳細的地圖,展示了各個函數之間的調用關系,讓我們一目了然地看到程序的執行流程。火焰圖也是 Perf Trace 功能的重要產物,火焰圖以一種直觀的方式展示了函數的執行時間和調用棧深度,越寬的部分表示該函數占用的時間越長,就像火焰的大小反映了火勢的強弱,幫助我們快速定位性能瓶頸所在。在一個復雜的應用程序中,可能存在多個函數相互調用,通過火焰圖,我們可以輕松地發現哪些函數占用了大量的時間,從而有針對性地進行優化。
(3)Profiling 功能
Profiling 功能使 Perf 能夠對特定的應用程序進行深入分析,找出其中最耗時的函數和代碼行。它提供了逐行統計的功能,就像一位細心的校對員,對每一行代碼的執行時間進行統計,讓我們清楚地知道每一行代碼對性能的影響。火焰圖在 Profiling 功能中同樣發揮著重要作用,通過火焰圖,我們可以從宏觀的角度看到整個程序的性能熱點,快速鎖定需要優化的區域。熱點分析也是 Perf Profiling 功能的重要組成部分,它能夠幫助我們找出程序中最常被訪問或執行時間最長的代碼區域,這些區域往往是性能優化的關鍵所在。在開發一個大型軟件項目時,可能存在一些核心算法或頻繁調用的函數,通過 Perf 的 Profiling 功能,我們可以準確地找出這些函數和代碼行,對其進行優化,從而提高整個軟件的性能。
(4)基準測試
在基準測試方面,Perf 可以與其他基準測試工具如 sysbench、fio 等結合使用,對系統的整體性能進行全面評估。與 sysbench 結合時,Perf 可以測量 CPU 在不同負載下的性能表現,比如在高并發的數據庫事務處理中,CPU 的運算速度、響應時間等指標,這對于評估服務器在實際業務場景中的性能非常重要。在內存性能評估上,Perf 與 fio 配合,能夠精準地測量內存的讀寫速度、帶寬利用率等參數。磁盤 IO 性能也是 Perf 的 “拿手好戲”,它可以測試磁盤的讀寫速率、尋道時間等關鍵指標,為系統調優提供全面而準確的參考數據。在構建一個大型數據中心時,需要對服務器的性能進行全面評估,Perf 與其他基準測試工具的結合使用,可以幫助我們了解服務器在不同工作負載下的性能表現,從而合理配置硬件資源,提高系統的整體性能。
(5)調試功能
Perf 還具備強大的調試功能,它可以與調試器如 gdb 結合使用,為開發人員提供更詳細的性能分析和調試信息。在程序運行時,Perf 能夠捕獲跟蹤數據,這些數據就像是程序運行的 “腳印”,記錄了程序的執行過程。并且,Perf 可以將這些跟蹤數據與源代碼進行關聯,就像將案件的線索與嫌疑人的行為聯系起來,讓開發人員能夠清晰地看到程序在執行過程中各個函數的調用情況、執行時間以及變量的變化等信息,從而更好地理解程序的運行機制,快速定位并解決性能問題。在開發一個復雜的軟件系統時,可能會遇到一些難以排查的性能問題,通過 Perf 與 gdb 的結合使用,開發人員可以深入分析程序的執行過程,找出問題的根源,提高軟件開發的效率和質量。
二、Perf安裝與使用
2.1安裝 Perf
在使用 Perf 之前,首先需要將其安裝到系統中。Perf 在不同的 Linux 發行版中有不同的安裝方式。
在基于 Debian 或 Ubuntu 的系統中,Perf 工具通常包含在linux-tools-common和linux-tools-<kernel-version>包中。安裝命令如下:
sudo apt-get install linux-tools-common linux-tools-`uname -r`
上述命令中,uname -r用于獲取當前系統的內核版本,linux-tools-<kernel-version>包會根據實際的內核版本進行安裝,確保 Perf 與系統內核版本兼容。
對于基于 Red Hat 或 CentOS 的系統,可以使用 yum 包管理器進行安裝:
sudo yum install perf
如果系統中沒有配置對應的 yum 源,可能需要先配置 yum 源,再進行安裝。yum 源的配置方式根據不同的系統和需求有所不同,一般可以通過下載官方的 yum 源配置文件,放置到/etc/yum.repos.d/目錄下,并根據實際情況修改文件中的參數,如baseurl、gpgcheck等。
從源碼安裝 Perf 則是另一種安裝方式,適合那些需要定制 Perf 功能或在沒有包管理器的環境中安裝的情況。首先,需要從 Linux 內核官方網站下載內核源碼,下載命令如下:
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
下載完成后,進入內核源碼目錄中的tools/perf子目錄,執行make命令進行編譯:
cd linux/tools/perf
make
編譯過程中,系統會檢查依賴庫的情況,如果缺少依賴庫,會提示需要安裝相應的庫。安裝完所有依賴庫后,再次執行make命令進行編譯。編譯完成后,在當前目錄下會生成一個名為perf的二進制文件,這就是 Perf 工具。可以將其復制到系統的可執行文件目錄中,如/usr/local/bin/,以便在系統的任何位置都能執行 Perf 命令:
sudo cp perf /usr/local/bin/
安裝完成后,可以通過運行perf --version命令來驗證 Perf 是否正確安裝。如果安裝成功,會顯示 Perf 的版本信息。
2.2Perf的基本使用
CPU周期(cpu-cycles)是默認的性能事件,所謂的CPU周期是指CPU所能識別的最小時間單元,通常為億分之幾秒,是CPU執行最簡單的指令時所需要的時間,例如讀取寄存器中的內容,也叫做clock tick。
perf COMMAND [-e event ...] PROGRAM, perf 是采用的這么一個命令格式, COMMAND一般常用的就是 top, stat, record, report等. 然后用 -e 參數來統計需要關注的事件. 多個事件就用多個 -e 連接。
Perf是一個包含22種子工具的工具集,以下是最常用的5種:
- perf-list
- perf-stat
- perf-top
- perf-record
- perf-report
- perf-trace
⑴perf-list
Perf-list用來查看perf所支持的性能事件,有軟件的也有硬件的。List all symbolic event types。
perf list [hw | sw | cache | tracepoint | event_glob]
⑵perf stat
說明一個工具的最佳途徑是列舉一個例子。考查下面這個例子程序。其中函數 longa() 是個很長的循環,比較浪費時間。函數 foo1 和 foo2 將分別調用該函數 10 次,以及 100 次。
//t1.c
void longa()
{
int i,j;
for(i = 0; i < 1000000; i++)
j=i; //am I silly or crazy? I feel boring and desperate.
}
void foo2()
{
int i;
for(i=0 ; i < 10; i++)
longa();
}
void foo1()
{
int i;
for(i = 0; i< 100; i++)
longa();
}
int main(void)
{
foo1();
foo2();
}
然后編譯它:
gcc -o t1 -g t1.c
下面演示了 perf stat 針對程序 t1 的輸出:
root@ubuntu-test:~# perf stat ./t1
Performance counter stats for './t1':
218.584169 task-clock # 0.997 CPUs utilized
18 context-switches # 0.000 M/sec
0 CPU-migrations # 0.000 M/sec
82 page-faults # 0.000 M/sec
771,180,100 cycles # 3.528 GHz
<not counted> stalled-cycles-frontend
<not counted> stalled-cycles-backend
550,703,114 instructions # 0.71 insns per cycle
110,117,522 branches # 503.776 M/sec
5,009 branch-misses # 0.00% of all branches
0.219155248 seconds time elapsed
程序 t1 是一個 CPU bound 型,因為 task-clock-msecs 接近 1
對 t1 進行調優應該要找到熱點 ( 即最耗時的代碼片段 ),再看看是否能夠提高熱點代碼的效率。缺省情況下,除了 task-clock-msecs 之外,perf stat 還給出了其他幾個最常用的統計信息:
- Task-clock-msecs:CPU 利用率,該值高,說明程序的多數時間花費在 CPU 計算上而非 IO。
- Context-switches:進程切換次數,記錄了程序運行過程中發生了多少次進程切換,頻繁的進程切換是應該避免的。
- Cache-misses:程序運行過程中總體的 cache 利用情況,如果該值過高,說明程序的 cache 利用不好
- CPU-migrations:表示進程 t1 運行過程中發生了多少次 CPU 遷移,即被調度器從一個 CPU 轉移到另外一個 CPU 上運行。
- Cycles:處理器時鐘,一條機器指令可能需要多個 cycles,Instructions: 機器指令數目。
- IPC:是 Instructions/Cycles 的比值,該值越大越好,說明程序充分利用了處理器的特性。
- Cache-references: cache 命中的次數,Cache-misses: cache 失效的次數。
通過指定 -e 選項,您可以改變 perf stat 的缺省事件 ( 關于事件,在上一小節已經說明,可以通過 perf list 來查看 )。假如您已經有很多的調優經驗,可能會使用 -e 選項來查看您所感興趣的特殊的事件。
有些程序慢是因為計算量太大,其多數時間都應該在使用 CPU 進行計算,這叫做 CPU bound 型;有些程序慢是因為過多的 IO,這種時候其 CPU 利用率應該不高,這叫做 IO bound 型;對于 CPU bound 程序的調優和 IO bound 的調優是不同的。
⑶perf top
使用 perf stat 的時候,往往您已經有一個調優的目標。比如我剛才寫的那個無聊程序 t1。
也有些時候,您只是發現系統性能無端下降,并不清楚究竟哪個進程成為了貪吃的 hog。
此時需要一個類似 top 的命令,列出所有值得懷疑的進程,從中找到需要進一步審查的家伙。
Perf top 用于實時顯示當前系統的性能統計信息。該命令主要用來觀察整個系統當前的狀態,比如可以通過查看該命令的輸出來查看當前系統最耗時的內核函數或某個用戶進程。
讓我們再設計一個例子來演示吧,我很快就想到了如代碼清單 2 所示的一個程序:
//t2.c
main(){
int i;
while(1) i++;
}
然后編譯這個程序:
gcc -o t2 -g t2.c
運行這個程序后, 我們另起一個窗口,運行perf top來看看:
Events: 8K cycles
98.67% t2 [.] main
1.10% [kernel] [k] __do_softirq
0.07% [kernel] [k] _raw_spin_unlock_irqrestore
0.05% perf [.] kallsyms__parse
0.05% libc-2.15.so [.] 0x807c7
0.05% [kernel] [k] kallsyms_expand_symbol
0.02% perf [.] map__process_kallsym_symbol
很容易便發現 t2 是需要關注的可疑程序。不過其作案手法太簡單:肆無忌憚地浪費著 CPU。所以我們不用再做什么其他的事情便可以找到問題所在。但現實生活中,影響性能的程序一般都不會如此愚蠢,所以我們往往還需要使用其他的 perf 工具進一步分析。
⑷使用 perf record, 解讀 report
使用 top 和 stat 之后,您可能已經大致有數了。要進一步分析,便需要一些粒度更細的信息。比如說您已經斷定目標程序計算量較大,也許是因為有些代碼寫的不夠精簡。那么面對長長的代碼文件,究竟哪幾行代碼需要進一步修改呢?這便需要使用 perf record 記錄單個函數級別的統計信息,并使用 perf report 來顯示統計結果。
您的調優應該將注意力集中到百分比高的熱點代碼片段上,假如一段代碼只占用整個程序運行時間的 0.1%,即使您將其優化到僅剩一條機器指令,恐怕也只能將整體的程序性能提高 0.1%。俗話說,好鋼用在刀刃上,不必我多說了。
perf record -e cpu-clock ./t1
perf report
perf report 輸出結果:
Events: 229 cpu-clock
100.00% t1 t1 [.] longa
不出所料,hot spot 是 longa( ) 函數。但,代碼是非常復雜難說的,t1 程序中的 foo1() 也是一個潛在的調優對象,為什么要調用 100 次那個無聊的 longa() 函數呢?但我們在上圖中無法發現 foo1 和 foo2,更無法了解他們的區別了。
我曾發現自己寫的一個程序居然有近一半的時間花費在 string 類的幾個方法上,string 是 C++ 標準,我絕不可能寫出比 STL 更好的代碼了。因此我只有找到自己程序中過多使用 string 的地方。因此我很需要按照調用關系進行顯示的統計信息。
使用 perf 的 -g 選項便可以得到需要的信息:
perf record -e cpu-clock -g ./t1
perf report
輸出結果:
Events: 270 cpu-clock
- 100.00% t1 t1 [.] longa
- longa
+ 91.85% foo1
+ 8.15% foo2
通過對 calling graph 的分析,能很方便地看到 91.85% 的時間都花費在 foo1() 函數中,因為它調用了 100 次 longa() 函數,因此假如 longa() 是個無法優化的函數,那么程序員就應該考慮優化 foo1,減少對 longa() 的調用次數。
⑸使用tracepoint
當 perf 根據 tick 時間點進行采樣后,人們便能夠得到內核代碼中的 hot spot。那什么時候需要使用 tracepoint 來采樣呢?
我想人們使用 tracepoint 的基本需求是對內核的運行時行為的關心,如前所述,有些內核開發人員需要專注于特定的子系統,比如內存管理模塊。這便需要統計相關內核函數的運行情況。另外,內核行為對應用程序性能的影響也是不容忽視的:
以之前的遺憾為例,假如時光倒流,我想我要做的是統計該應用程序運行期間究竟發生了多少次系統調用。在哪里發生的?
下面我用 ls 命令來演示 sys_enter 這個 tracepoint 的使用:
root@ubuntu-test:~# perf stat -e raw_syscalls:sys_enter ls
bin libexec off perf.data.old t1 t3 tutong.iso
bwtest minicom.log perf.data pktgen t1.c t3.c
Performance counter stats for 'ls':
111 raw_syscalls:sys_enter
0.001557549 seconds time elapsed
個報告詳細說明了在 ls 運行期間發生了多少次系統調用 ( 上例中有 111 次 )。
2.3常用命令詳解
Perf 提供了豐富的命令和參數,以滿足不同的性能分析需求。下面詳細介紹一些常用的命令及參數。
(1)列出所有可監測事件
perf list命令用于列出系統中所有可監測的性能事件,這些事件包括硬件性能事件、軟件性能事件以及 tracepoints。硬件性能事件由 CPU 硬件提供,如cycles表示 CPU 時鐘周期計數,instructions表示執行的指令數,cache-misses表示緩存未命中計數等;軟件性能事件由內核軟件提供,例如context-switches表示上下文切換的次數,page-faults表示頁面錯誤的次數,cpu-migrations表示 CPU 遷移的次數等;tracepoints 則是內核中靜態 tracepoint 所觸發的事件,用來判斷程序運行期間內核的行為細節 。使用perf list命令可以查看系統支持的所有事件類型,例如:
$ perf list
List of pre-defined events (to be used in -e):
branch-instructions OR branches [Hardware event]
branch-misses [Hardware event]
bus-cycles [Hardware event]
cache-misses [Hardware event]
cache-references [Hardware event]
cpu-cycles OR cycles [Hardware event]
instructions [Hardware event]
alignment-faults [Software event]
bpf-output [Software event]
context-switches OR cs [Software event]
cpu-clock [Software event]
cpu-migrations OR migrations [Software event]
dummy [Software event]
emulation-faults [Software event]
major-faults [Software event]
minor-faults [Software event]
page-faults OR faults [Software event]
task-clock [Software event]
duration_time [Tool event]
L1-dcache-load-misses [Hardware cache event]
L1-dcache-loads [Hardware cache event]
L1-dcache-stores [Hardware cache event]
L1-icache-load-misses [Hardware cache event]
L1-icache-loads [Hardware cache event]
branch-load-misses [Hardware cache event]
branch-loads [Hardware cache event]
dTLB-load-misses [Hardware cache event]
dTLB-loads [Hardware cache event]
dTLB-store-misses [Hardware cache event]
dTLB-stores [Hardware cache event]
iTLB-load-misses [Hardware cache event]
iTLB-loads [Hardware cache event]
node-load-misses [Hardware cache event]
node-loads [Hardware cache event]
node-store-misses [Hardware cache event]
node-stores [Hardware cache event]
branch-instructions OR cpu/branch-instructions/ [Kernel PMU event]
branch-misses OR cpu/branch-misses/ [Kernel PMU event]
bus-cycles OR cpu/bus-cycles/ [Kernel PMU event]
cache-misses OR cpu/cache-misses/ [Kernel PMU event]
cache-references OR cpu/cache-references/ [Kernel PMU event]
cpu-cycles OR cpu/cpu-cycles/ [Kernel PMU event]
instructions OR cpu/instructions/ [Kernel PMU event]
mem-loads OR cpu/mem-loads/ [Kernel PMU event]
mem-stores OR cpu/mem-stores/ [Kernel PMU event]
ref-cycles OR cpu/ref-cycles/ [Kernel PMU event]
topdown-fetch-bubbles OR cpu/topdown-fetch-bubbles/ [Kernel PMU event]
topdown-recovery-bubbles OR cpu/topdown-recovery-bubbles/ [Kernel PMU event]
topdown-slots-issued OR cpu/topdown-slots-issued/ [Kernel PMU event]
topdown-slots-retired OR cpu/topdown-slots-retired/ [Kernel PMU event]
topdown-total-slots OR cpu/topdown-total-slots/ [Kernel PMU event]
通過查看這些事件,可以了解系統中哪些性能指標是可以被監測的,從而在進行性能分析時選擇合適的事件進行監測。
(2)顯示統計信息
perf stat命令用于分析指定程序或命令的性能概況,它會在程序執行結束后,輸出各類事件的統計信息,幫助用戶了解程序在運行過程中的性能表現。例如,使用perf stat命令監測ls命令的性能:
$ perf stat ls
Performance counter stats for 'ls':
0.653782 task-clock (msec) # 0.691 CPUs utilized
0 context-switches # 0.000 K/sec
0 CPU-migrations # 0.000 K/sec
247 page-faults # 0.378 M/sec
1,625,426 cycles # 2.486 GHz
1,050,293 stalled-cycles-frontend # 64.62% frontend cycles idle
838,781 stalled-cycles-backend # 51.60% backend cycles idle
1,055,735 instructions # 0.65 insns per cycle
# 0.99 stalled cycles per insn
210,587 branches # 322.106 M/sec
10,809 branch-misses # 5.13% of all branches
0.000945883 seconds time elapsed
上述輸出中,task-clock表示任務真正占用的處理器時間,單位為毫秒;CPUs utilized表示 CPU 的占用率,通過task-clock除以time elapsed計算得出;context-switches表示上下文的切換次數;CPU-migrations表示處理器遷移次數,Linux 為了維持多個處理器的負載均衡,在特定條件下會將某個任務從一個 CPU 遷移到另一個 CPU;page-faults表示缺頁異常的次數,當應用程序請求的頁面尚未建立、請求的頁面不在內存中,或者請求的頁面雖然在內存中,但物理地址和虛擬地址的映射關系尚未建立時,都會觸發一次缺頁異常;cycles表示消耗的處理器周期數;instructions表示執行的指令數;branches表示遇到的分支指令數;branch-misses表示預測錯誤的分支指令數;time elapsed表示程序持續時間。
perf stat命令還可以通過-e選項指定要顯示統計的性能事件,例如:
$ perf stat -e cache-misses,cache-references ls
Performance counter stats for 'ls':
13,808 cache-misses # 14.603 M/sec
53,932 cache-references # 57.029 M/sec
0.000945883 seconds time elapsed
上述命令只統計了cache-misses和cache-references事件,幫助用戶更專注地了解程序在緩存方面的性能表現。
(3)實時查看系統性能
perf top命令用于實時顯示系統或進程的性能統計信息,它會動態地展示占用最多 CPU 時間的函數或程序,類似于top命令,但更加專注于性能分析。直接運行perf top命令,將會展示系統中所有的進程和函數,按照它們占用 CPU 時間的百分比降序排列:
$ perf top
Samples: 1M of event 'cycles', Event count (approx.): 73891391490
5.44% perf [.] 0x0000000000023256
4.86% [kernel] [k] _spin_lock
2.43% [kernel] [k] _spin_lock_bh
2.29% [kernel] [k] _spin_lock_irqsave
1.77% [kernel] [k] __d_lookup
1.55% libc-2.12.so [.] __strcmp_sse42
1.43% nginx [.] ngx_vslprintf
1.37% [kernel] [k] tcp_poll
第一列表示符號引發的性能事件的比例,默認指占用的 CPU 周期比例;第二列表示符號所在的 DSO(Dynamic Shared Object),可以是應用程序、內核、動態鏈接庫、模塊;第三列表示 DSO 的類型,[.]表示此符號屬于用戶態的 ELF 文件,包括可執行文件與動態鏈接庫,[k]表示此符號屬于內核或模塊;第四列表示符號名,有些符號不能解析為函數名,只能用地址表示。
如果只想關注特定的進程,可以使用-p選項,后面跟上進程的 PID:
$ perf top -p <PID>
使用-e選項可以指定一個特定的性能事件,比如關心緩存未命中(cache misses):
$ perf top -e cache-misses
-a選項用于查看所有 CPU 的數據,而不僅僅是默認的 CPU 0:
$ perf top -a
-K選項可以隱藏內核相關的符號,如果只對用戶空間的性能感興趣,這個選項會非常有用:
$ perf top -K
在運行perf top時,需要確保有足夠的權限,大多數情況下,需要 root 權限才能運行它。并且,perf top的結果是實時更新的,用戶只需要保持窗口開啟,就可以實時觀察到系統性能的變化。
(4)記錄與生成報告
perf record命令用于收集程序運行時的性能數據,并將數據保存到一個名為perf.data的文件中。例如,使用perf record命令記錄ls命令的性能數據:
$ perf record -g ls
上述命令中,-g選項用于記錄函數間的調用關系,方便后續分析函數的調用棧。運行該命令后,ls命令的性能數據會被記錄到perf.data文件中。
perf report命令用于分析perf record生成的perf.data文件,并顯示分析報告。運行perf report命令后,會進入一個交互式界面,展示性能數據的詳細分析結果,包括函數的調用關系、每個函數占用的 CPU 時間等信息:
$ perf report -i perf.data
-i選項用于指定要分析的perf.data文件,如果不指定,默認分析當前目錄下的perf.data文件。在交互式界面中,可以使用方向鍵上下移動選擇不同的函數,按回車鍵可以查看函數的詳細信息,如匯編代碼、調用棧等。還可以使用/鍵進行搜索,輸入關鍵詞來查找特定的函數或符號。
此外,perf report命令還可以通過其他參數進行更詳細的分析。例如,-d選項只顯示指定 DSO 的符號,-C選項只顯示指定 comm(觸發事件的進程名)的信息,-S選項只考慮指定符號,-U選項只顯示已解析的符號,-g[type,min,order]選項顯示調用關系,具體等同于perf top命令中的-g選項 。通過這些參數的組合使用,可以根據具體需求對性能數據進行更有針對性的分析。
三、Perf的應用場景與重要性
3.1性能問題定位
perf 在解決系統性能問題方面發揮著至關重要的作用。當面臨 CPU 利用率過高的情況時,perf 可以通過收集性能數據,分析各個進程和函數對 CPU 資源的占用情況,找出導致高 CPU 使用率的熱點代碼段。例如,通過 perf top 命令可以實時顯示當前系統中消耗 CPU 周期最多的函數或指令,快速定位可能存在性能問題的代碼區域。
在 cache miss 過多的場景下,perf 能夠評估程序對各級 cache 的訪問次數和丟失次數。利用 perf stat 命令可以查看與 cache 相關的性能事件,如 L1-dcache-loads、L1-dcache-load-miss 等,了解 cache 的利用情況。如果發現 cache miss 率過高,可以進一步分析代碼,優化數據訪問模式以提高 cache 命中率。
對于內存 I/O 過慢的問題,perf 可以評估程序的內存訪問行為。通過收集與內存相關的性能事件,如 page-faults、dTLB-loads、dTLB-load-miss 等,分析內存訪問的效率。如果頻繁出現頁錯誤或者 TLB 未命中,可能需要調整內存分配策略或者優化數據結構,以減少內存訪問的開銷。
此外,perf 還可以生成程序的調用圖,記錄函數之間的調用關系。通過 perf record 和 perf report 命令,可以收集程序運行時的性能數據,并生成詳細的報告,包括函數調用圖、耗時分布等信息。這有助于理解程序的執行流程,找出可能存在性能瓶頸的函數調用鏈。
同時,perf 還能檢測程序的內存泄漏問題。雖然文章中未明確提及 perf 在內存泄漏檢測方面的具體方法,但可以推測,通過對內存相關性能事件的監測以及對程序運行時內存使用情況的分析,可能能夠發現內存泄漏的跡象。例如,持續觀察內存使用量的增長情況,結合函數調用圖分析可能存在內存分配但未釋放的位置。
3.2性能優化關鍵
perf 在程序性能優化中具有不可替代的重要性。它可以深入了解應用程序的執行過程,為開發者提供關鍵的性能指標和分析結果,幫助發現性能瓶頸并進行針對性優化。
通過追蹤 CPU 使用情況,開發者可以了解程序在不同階段對 CPU 資源的消耗情況。利用 perf stat 命令可以獲取諸如 cycles、instructions、IPC 等指標,分析程序是否充分利用了處理器的特性。如果 IPC 值較低,可能需要優化代碼以提高指令的執行效率,減少不必要的指令或循環。
內存占用的追蹤也是性能優化的重要方面。perf 可以監測內存的使用情況,包括內存分配、釋放以及 cache 的利用情況。通過分析這些數據,可以優化內存管理策略,減少內存碎片,提高內存的使用效率。例如,合理調整數據結構的大小和布局,避免頻繁的內存分配和釋放操作。
函數調用堆棧的追蹤可以幫助開發者找出熱點函數,即那些消耗大量資源的函數。利用 perf top 和 perf record 命令可以收集函數級別的性能數據,確定哪些函數是性能瓶頸所在。針對熱點函數,可以進行算法優化或代碼重構,提高其執行效率。
四、常見性能問題分析
⑴性能測試大致分以下幾個步驟:
- 需求分析
- 腳本準備
- 測試執行
- 結果整理
- 問題分析
需求描述:有一個服務,啟動時會加載一個1G的詞表文件到內存,請求來了之后,會把請求詞去詞表里做模糊匹配,如果匹配到了就向一個后端服務發送一條http請求,拿回數據之后,返回給客戶端的同時,向mysql記錄請求的唯一標識和一個請求次數的標記;
- 其中有幾個關鍵函數
- 模糊匹配(fuzzyMatching)
- 后端請求函數(sendingRequest)
- 拼裝請求函數(buildResponse)
- 記錄mysql請求次數標記(signNum)
問題及分析:
第一組:完全隨機請求詞,qps達到1k時,服務器未見異常,cpu、內存、帶寬均未滿,qps無法繼續提升;
分析:由于此服務后端連接了其它服務,所以在壓測之前,要確認后端服務不會成為瓶頸點,目前的狀態很可能是后端服務限制了被測服務的性能;此時可以檢查后端服務所在機器的各項指標,或者查看本機的連接狀況,一般后端服務無法處理,而被測服務又會一直向后面請求的話,timewait狀態的連接會變得比較多;
第二組:解決后端服務的問題后,第二組使用平均30個字的請求詞,來打壓,qps到400時,cpu load已滿;
分析:這種情況明顯是由于fuzzyMatching函數計算效率的問題導致cpu滿載,從而無法提升qps,使響應時間不斷增大,此時可以通過perf+火焰圖來確定整個處理請求過程中響應時間長的函數;此時需要評估壓測數據是否合理,如果線上平均請求詞只有2個的時候,此組測試明顯不合理,此時要開發進行性能優化就是浪費時間的;如果評估測試數據合理,可以再次更換短詞數據進行壓測驗證猜測;
第三組:解決了上述兩個問題之后,使用完全隨機請求詞,qps到達3k后降低至1k,然后再次提升到3k,如此反復;
分析:此時關注一下各項指標,排除了以上的問題的話,操作mysql慢的問題可能性大一些,對這種需要高并發的系統來說,直接讀寫mysql不是個聰明的解決方案,一般會用redis做一層緩存,這里說道的另一個問題就是開發設計不合理,導致的性能問題;
第四組:將后端換做真實的服務來做整體壓測,發現qps最高只能到300,此時檢查各項指標,發現入口帶寬占滿了;
分析:這次問題比較明顯,后端服務返回內容過大,導致帶寬被占滿,此時依然需要評估需求:1、是否需要后端返回的所有數據內容;2、評估更換萬兆網卡的性價比;3、是否可以通過技術手段優化帶寬占用,比如把一次請求分散到多組服務的多個請求;
⑵perf+火焰圖定位函數問題
這里簡單說一下如何使用perf+火焰圖來直觀的定位性能問題:
Perf 擁有了眾多的性能分析能力,舉例來說,使用 Perf 可以計算每個時鐘周期內的指令數,稱為 IPC,IPC 偏低表明代碼沒有很好地利用 CPU。Perf 還可以對程序進行函數級別的采樣,從而了解程序的性能瓶頸究竟在哪里等等。Perf 還可以替代 strace,可以添加動態內核 probe 點,還可以做 benchmark 衡量調度器的好壞。
- 使用舉例:perf record -e cpu-clock -g -p 11110 -o data/perf.data sleep 30
- -g 選項是告訴perf record額外記錄函數的調用關系 -e cpu-clock 指perf record監控的指標為cpu周期 -p 指定需要record的進程pid
⑶生成火焰圖
①第一步:使用壓力測試工具對程序進行打壓,壓到程序拐點;
$sudo perf record -e cpu-clock -g -p 11110
Ctrl+c結束執行后,在當前目錄下會生成采樣數據perf.data.
②第二步:用perf 工具對perf.data進行解析
perf -i perf.data &> perf.unfold
⑶第三步:將perf.unfold中的符號進行折疊:
./stackcollapse-perf.pl perf.unfold &> perf.folded
④最后生成svg圖:
./flamegraph.pl perf.folded > perf.svg
到這兒可以生成函數調用火焰圖,如下圖:
圖片
原生的perf可以直接定位C/C++的程序,通常編譯debug版本的程序能看到更多的信息,java、go等語言可以通過各自定制的工具來生成,原理類似;通過火焰圖可以輕松定位到哪個函數的處理時間最長,從而找到問題所在。