使用GProf來優化你的C/C++程式
摘要:
在優化程式的時候,要記住:在值得優化的地方優化!沒有必要花上幾個小時來優化一段實際上只執行0.04秒的程式。
GProf 使用了一種異常簡單但是非常有效的方法來優化C/C++ 程式,而且能很容易的識別出值得優化的程式碼。一個簡單的案例分析將會顯示,GProf如何通過識別並優化兩個關鍵的資料結構,將實際應用中的程式從3分鐘的執行時優化到5秒的。
這個程式最早可以追溯到1982年關於編譯器構建的特別討論大會(the SIGPLAN Symposium on Compiler Construction)。現在這個程式成了各種UNIX 平臺上的一個標準工具。
Profiling in a nutshell
程式概要分析的概念非常簡單:通過記錄各個函式的呼叫和結束時間,我們可以計算出程式的最大執行時的程式段。這種方法聽起來似乎要花費很多氣力——幸運的是,我們其實離真理並不遠!我們只需要在用 gcc 編譯時加上一個額外的引數('-pg'),執行這個(編譯好的)程式(來蒐集程式概要分析的有關資料),然後執行'gprof'以更方便的分析這些結果。
案例分析: Pathalizer
我使用了一個現實中使用的程式來作為例子,是 pathalizer的一部分: 即event2dot
,一個將路徑“事件”描述檔案轉化為圖形化“dot”檔案的工具(executable which translates a pathalizer 'events' file to a graphviz 'dot' file)。
簡單的說,它從一個檔案裡面讀取各種事件,然後將它們分別儲存為影象(以頁為節點,且將頁與頁之間的轉變作為邊),然後將這些影象整合為一張大的圖形,並儲存為圖形化的'dot'格式檔案。
給程式計時
先讓我們給我們未經優化的程式計一下時,看看它們的執行要多少時間。在我的計算機上使用event2dot
並用原始碼裡的例子作為輸入(大概55000的資料),大致要三分多鐘:
real 3m36.316s user 0m55.590s sys 0m1.070s
程式分析
要使用gprof 作概要分析,在編譯的時候要加上'-pg' 選項,我們就是如下重新編譯原始碼如下:
g++ -pg dotgen.cpp readfile.cpp main.cpp graph.cpp config.cpp -o event2dot
現在我們可以再次執行event2dot
,並使用我們前面使用的測試資料。這次我們執行的時候,event2dot
執行的分析資料會被蒐集並儲存在'gmon.out'檔案中,我們可以通過執行'gprof event2dot
| less'來檢視結果。
gprof 會顯示出如下的函式比較重要:
% cumulative self self total time seconds seconds calls s/call s/call name 43.32 46.03 46.03 339952989 0.00 0.00 CompareNodes(Node *,Node *) 25.06 72.66 26.63 55000 0.00 0.00 getNode(char *,NodeListNode *&) 16.80 90.51 17.85 339433374 0.00 0.00 CompareEdges(Edge *,AnnotatedEdge *) 12.70 104.01 13.50 51987 0.00 0.00 addAnnotatedEdge(AnnotatedGraph *,Edge *) 1.98 106.11 2.10 51987 0.00 0.00 addEdge(Graph *,Node *,Node *) 0.07 106.18 0.07 1 0.07 0.07 FindTreshold(AnnotatedEdge *,int) 0.06 106.24 0.06 1 0.06 28.79 getGraphFromFile(char *,NodeListNode *&,Config *) 0.02 106.26 0.02 1 0.02 77.40 summarize(GraphListNode *,Config *) 0.00 106.26 0.00 55000 0.00 0.00 FixName(char *)
可以看出,第一個函式比較重要: 程式裡面絕大部分的執行時都被它給佔據了。
優化
上面結果可以看出,這個程式大部分的時間都花在了CompareNodes
函式上,用 grep 檢視一下則發現CompareNodes 只是被CompareEdges
呼叫了一次而已, 而CompareEdges則只被addAnnotatedEdge
呼叫——它們都出現在了上面的清單中。這兒就是我們應該做點優化的地方了吧!
我們注意到addAnnotatedEdge
遍歷了一個連結串列。雖然連結串列是易於實現,但是卻實在不是最好的資料型別。我們決定將連結串列 g->edges 用二叉樹來代替: 這將會使得查詢更快。
結果
現在我們看一下優化後的執行結果:
real 2m19.314s user 0m36.370s sys 0m0.940s
第二遍
再次執行 gprof 來分析:
% cumulative self self total time seconds seconds calls s/call s/call name 87.01 25.25 25.25 55000 0.00 0.00 getNode(char *,NodeListNode *&) 10.65 28.34 3.09 51987 0.00 0.00 addEdge(Graph *,Node *,Node *)
看起來以前佔用大量執行時的函式現在已經不再是佔用執行時的大頭了!我們試一下再優化一下呢:用節點雜湊表來取代節點樹。
這次簡直是個巨大的進步:
real 0m3.269s user 0m0.830s sys 0m0.090s
其他 C/C++ 程式分析器
還有其他很多分析器可以使用gprof 的資料, 例如KProf (截圖) 和 cgprof。雖然圖形介面的看起來更舒服,但我個人認為命令列的gprof 使用更方便。
對其他語言的程式進行分析
我們這裡介紹了用gprof 來對C/C++ 的程式進行分析,對其他語言其實一樣可以做到: 對 Perl,我們可以用Devel::DProf 模組。你的程式應該以perl -d:DProf mycode.pl
來開始,並使用dprofpp
來檢視並分析結果。如果你可以用gcj 來編譯你的Java 程式,你也可以使用gprof,然而目前還只支援單執行緒的Java 程式碼。
結論
就像我們已經看到的,我們可以使用程式概要分析快速的找到一個程式裡面值得優化的地方。在值得優化的地方優化,我們可以將一個程式的執行時從 3分36秒 減少到少於 5秒,就像從上面的例子看到的一樣。
References
(http://www.fanqiang.com)