最近線上運作的程式出現性能問題,但通過分析程式源代碼(Code Review),并找不到導緻問題的根本原因。是以,隻能借助強大的性能分析工具 perf 來找出問題所在。
perf 工具的功能非常強大,但本文并不是介紹 perf 工具的使用,而是介紹 perf 的實作原理。介紹 perf 使用的文章多如牛毛,但介紹 perf 原理和實作的卻鳳毛麟角。
但正因為 perf 功能非常強大,是以其實作也是非常複雜的。本文隻介紹其中的一個功能:分析程序中的函數調用頻率 。
接下來,我們先介紹怎麼使用 perf 來分析程序中的函數調用頻率。
使用 perf 分析程式性能瓶頸
在介紹 perf 的實作之前,我們先使用 perf 分析一個簡單的程式,此程式代碼如下:
// sample.c
void workload1()
{
int i, c = 0;
for (i = 0; i < 100000000; i++) {
c += i * i;
c -= i * 100;
c += i * i * i / 100;
}
}
void workload2()
{
int i, c = 0;
for (i = 0; i < 200000000; i++) {
c += i * i;
c -= i * 100;
c += i * i * i / 100;
}
}
int main(int argc, char *argv[])
{
workload1();
workload2();
return 0;
}
上面的程式很簡單,我們建立兩個函數:workload1 和 workload2。從代碼可以看出,workload2 的負載是 workload1 的2倍。
現在我們使用 perf 來分析這個程式的性能瓶頸在哪裡。
- 首先我們将程式編譯成可執行檔案,編譯時記得加上 -g 參數,這樣 perf 才能擷取到函數名。
$ gcc sample.c -g -o sample
- 使用 perf 的 record 指令來記錄程式的運作情況。
$ sudo perf record -g ./sample sleep 10
運作上面的指令後,将會生成一個 perf.data 的檔案,此檔案記錄了 sample 程式運作時的采樣資料。
- 使用 perf 的 report 指令分析程式的運作情況。
$ perf report -g
結果如下圖所示:
從上圖可以看出,函數 workload2(65%)的負載大概是函數 workload1(35%)的 2 倍,與我們的代碼基本一緻。
更多linux核心視訊教程文檔資料免費領取背景私信【核心】自行擷取.
perf 實作原理
通過上面的例子,我們大概知道怎麼使用 perf 來分析程式的性能瓶頸。接下來,我們将會介紹 perf 的内部實作原理。
來思考一下,如果讓我們來設計一個統計程式中各個函數占用 CPU 時間的方案,應該如何設計?最簡單的方案就是:在各個函數的開始記錄目前時間,然後在函數執行結束後,使用目前時間減去函數開始執行時的時間,得到函數的執行時間總時長。如下僞代碼:
void func1()
{
...
}
void func2()
{
...
}
int main(int argc, char *argv[])
{
int start_time, total_time;
start_time = now();
func1();
total_time = now() - start_time;
printf("func1() spent %d\n", total_time);
start_time = now();
func2();
total_time = now() - start_time;
printf("func2() spent %d\n", total_time);
}
雖然上述方式可以統計程式中各個函數的耗時情況,但卻存在很多問題:
- 代碼入侵度高。由于要對每個函數進行耗時記錄,是以必須在調用函數前和調用函數後加入統計代碼。
- 統計函數耗時,并不能反映該函數的真實 CPU 使用率。比如函數内部調用了導緻程序休眠的系統調用(如sleep),這時函數實際上是不使用CPU的,但函數的耗時卻統計了休眠的時間。
- 對性能影響較大。由于程式中所有函數都加入統計代碼,是以對性能的影響是非常大的。
是以我們需要一個系統,它能夠避免上述問題:
- 零代碼入侵。
- 能夠真實反映函數的 CPU 使用率。
- 對性能影響較小。
perf 就是為了解決上述問題而生的,我們先來介紹一下 perf 的原理。
采樣
為了減小對程式性能的影響,perf 并不會在每個函數加入統計代碼,取而代之的統計方式是:采樣。
采樣的原理是:設定一個定時器,當定時器觸發時,檢視目前程序正在執行的函數,然後記錄下來。如下圖所示:
如上圖所示,每個 cpu-clock 是一個定時器的觸發點。在 6 次定時器觸發點中,函數 func1 被命中了 3 次,函數 func2 被命中了 1 次,函數 func3 被命中了 2 次。是以,我們可以推測出,函數 func1 的 CPU 使用率最高。
排序
如果程式有成千上萬的函數,那麼采樣出來的資料可能非常多,這個時候就需要對采樣的資料進行排序。
為了對采樣資料進行排序,perf 使用紅黑樹這種資料結構,如下圖所示:
如上圖所示,在 perf 采樣的資料中,有 7 個函數被統計了命中次數,perf 使用采樣到的資料建構一棵紅黑樹。
根據紅黑樹的特性,最右邊的節點就是被命中最多的函數,這樣就能把程式中 CPU 使用率最高的函數找出來。
總結
由于 perf 的功能非常強大,是以本文也隻介紹了 perf 其中一種功能:統計函數的 CPU 使用率。
在下一篇文章中,我們将會介紹 perf 的代碼實作。Linux 的創始人 Linus 曾經說過:Read the f**king source code,要真正了解一個系統,隻能通過閱讀其源碼。
原文作者:Linux核心那些事