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” 檔案的工具(executablewhichtranslatesapathalizer'events'filetoagraphviz'dot'file)。
簡單的說,它從一個檔案裡面讀取各種事件,然後將它們分別儲存為映像(以頁為節點,且將頁與頁之間的轉變作為邊),然後將這些映像整合為一張大的圖形,並儲存為圖形化的'dot'格式檔案。
給程式計時
先讓我們給我們未經最佳化的程式計一下時,看看它們的運行要多少時間。在我的電腦上使用event2dot並用源碼裡的例子作為輸入(大概55000的資料),大致要三分多鐘:
real3m36.316s
user0m55.590s
sys0m1.070s
程式分析
要使用gprof作概要分析,在編譯的時候要加上'-pg'選項,我們就是如下重新編譯源碼如下:
g++-pgdotgen.cppreadfile.cppmain.cppgraph.cppconfig.cpp-oevent2dot
現在我們可以再次運行event2dot,並使用我們前面使用的測試資料。這次我們啟動並執行時候,event2dot啟動並執行分析資料會被搜集並儲存在'gmon.out'檔案中,我們可以通過運行'gprofevent2dot|less'來查看結果。
gprof會顯示出如下的函數比較重要:
%cumulativeselfselftotal
timesecondssecondscallss/calls/callname
43.3246.0346.033399529890.000.00CompareNodes(Node*,Node*)
25.0672.6626.63550000.000.00getNode(char*,NodeListNode*&)
16.8090.5117.853394333740.000.00CompareEdges(Edge*,AnnotatedEdge*)
12.70104.0113.50519870.000.00addAnnotatedEdge(AnnotatedGraph*,Edge*)
1.98106.112.10519870.000.00addEdge(Graph*,Node*,Node*)
0.07106.180.0710.070.07FindTreshold(AnnotatedEdge*,int)
0.06106.240.0610.0628.79getGraphFromFile(char*,NodeListNode*&,Config*)
0.02106.260.0210.0277.40summarize(GraphListNode*,Config*)
0.00106.260.00550000.000.00FixName(char*)
可以看出,第一個函數比較重要:程式裡面絕大部分的運行時都被它給佔據了。
最佳化
上面結果可以看出,這個程式大部分的時間都花在了CompareNodes函數上,用grep查看一下則發現CompareNodes只是被 CompareEdges調用了一次而已,而CompareEdges則只被addAnnotatedEdge調用——它們都出現在了上面的清單中。這兒就是我們應該做點最佳化的地方了吧!
我們注意到addAnnotatedEdge遍曆了一個鏈表。雖然鏈表是易於實現,但是卻實在不是最好的資料類型。我們決定將鏈表g->edges用二叉樹來代替:這將會使得尋找更快。
結果
現在我們看一下最佳化後的運行結果:
real2m19.314s
user0m36.370s
sys0m0.940s
第二遍
再次運行gprof來分析:
%cumulativeselfselftotal
timesecondssecondscallss/calls/callname
87.0125.2525.25550000.000.00getNode(char*,NodeListNode*&)
10.6528.343.09519870.000.00addEdge(Graph*,Node*,Node*)
看起來以前佔用大量運行時的函數現在已經不再是佔用運行時的大頭了!我們試一下再最佳化一下呢:用節點雜湊表來取代節點樹。
這次簡直是個巨大的進步:
real0m3.269s
user0m0.830s
sys0m0.090s
其他C/C++程式分析器
還有其他很多分析器可以使用gprof的資料,例如
KProf(截屏)和cgprof。雖然圖形介面的看起來更舒服,但我個人認為命令列的gprof使用更方便。
對其他語言的程式進行分析
我們這裡介紹了用gprof來對C/C++的程式進行分析,對其他語言其實一樣可以做到:對Perl,我們可以用Devel::DProf模組。你的程式應該以perl-d:DProfmycode.pl來開始,並使用dprofpp來查看並分析結果。如果你可以用gcj來編譯你的Java程式,你也可以使用gprof,然而目前還只支援單線程的Java代碼。
結論
就像我們已經看到的,我們可以使用程式概要分析快速的找到一個程式裡面值得最佳化的地方。在值得最佳化的地方最佳化,我們可以將一個程式的運行時從3分36秒減少到少於5秒,就像從上面的例子看到的一樣。