GProf 使用了一種異常簡(jiǎn)單但是非常有效的方法來(lái)優(yōu)化C/C++ 程序,而且能很容易的識(shí)別出值得優(yōu)化的代碼。一個(gè)簡(jiǎn)單的案例分析將會(huì)顯示,GProf如何通過(guò)識(shí)別并優(yōu)化兩個(gè)關(guān)鍵的數(shù)據(jù)結(jié)構(gòu),將實(shí)際應(yīng)用中的程序從3分鐘的運(yùn)行時(shí)優(yōu)化到5秒的。
這個(gè)程序最早可以追溯到1982年關(guān)于編譯器構(gòu)建的特別討論大會(huì)(the SIGPLAN Symposium on Compiler Construction)。現(xiàn)在這個(gè)程序成了各種UNIX 平臺(tái)上的一個(gè)標(biāo)準(zhǔn)工具。
_________________ _________________ _________________
Profiling in a nutshell
程序概要分析的概念非常簡(jiǎn)單:通過(guò)記錄各個(gè)函數(shù)的調(diào)用和結(jié)束時(shí)間,我們可以計(jì)算出程序的最大運(yùn)行時(shí)的程序段。這種方法聽(tīng)起來(lái)似乎要花費(fèi)很多氣力——幸運(yùn)的是,我們其實(shí)離真理并不遠(yuǎn)!我們只需要在用 gcc 編譯時(shí)加上一個(gè)額外的參數(shù)('-pg'),運(yùn)行這個(gè)(編譯好的)程序(來(lái)搜集程序概要分析的有關(guān)數(shù)據(jù)),然后運(yùn)行'gprof'以更方便的分析這些結(jié)果。
案例分析: Pathalizer
我使用了一個(gè)現(xiàn)實(shí)中使用的程序來(lái)作為例子,是 pathalizer的一部分: 即event2dot,一個(gè)將路徑“事件”描述文件轉(zhuǎn)化為圖形化“dot”文件的工具(executable which translates a pathalizer 'events' file to a graphviz 'dot' file)。
簡(jiǎn)單的說(shuō),它從一個(gè)文件里面讀取各種事件,然后將它們分別保存為圖像(以頁(yè)為節(jié)點(diǎn),且將頁(yè)與頁(yè)之間的轉(zhuǎn)變作為邊),然后將這些圖像整合為一張大的圖形,并保存為圖形化的'dot'格式文件。
給程序計(jì)時(shí)
先讓我們給我們未經(jīng)優(yōu)化的程序計(jì)一下時(shí),看看它們的運(yùn)行要多少時(shí)間。在我的計(jì)算機(jī)上使用event2dot并用源碼里的例子作為輸入(大概55000的數(shù)據(jù)),大致要三分多鐘:
real 3m36.316s
user 0m55.590s
sys 0m1.070s
程序分析
要使用gprof 作概要分析,在編譯的時(shí)候要加上'-pg' 選項(xiàng),我們就是如下重新編譯源碼如下:
g++ -pg dotgen.cpp readfile.cpp main.cpp graph.cpp config.cpp -o event2dot
現(xiàn)在我們可以再次運(yùn)行event2dot,并使用我們前面使用的測(cè)試數(shù)據(jù)。這次我們運(yùn)行的時(shí)候,event2dot運(yùn)行的分析數(shù)據(jù)會(huì)被搜集并保存在'gmon.out'文件中,我們可以通過(guò)運(yùn)行'gprof event2dot | less'來(lái)查看結(jié)果。
gprof 會(huì)顯示出如下的函數(shù)比較重要:
% 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 *)
可以看出,第一個(gè)函數(shù)比較重要: 程序里面絕大部分的運(yùn)行時(shí)都被它給占據(jù)了。
優(yōu)化
上面結(jié)果可以看出,這個(gè)程序大部分的時(shí)間都花在了CompareNodes函數(shù)上,用 grep 查看一下則發(fā)現(xiàn)CompareNodes 只是被CompareEdges調(diào)用了一次而已, 而CompareEdges則只被addAnnotatedEdge調(diào)用——它們都出現(xiàn)在了上面的清單中。這兒就是我們應(yīng)該做點(diǎn)優(yōu)化的地方了吧!
我們注意到addAnnotatedEdge遍歷了一個(gè)鏈表。雖然鏈表是易于實(shí)現(xiàn),但是卻實(shí)在不是最好的數(shù)據(jù)類型。我們決定將鏈表 g->edges 用二叉樹(shù)來(lái)代替: 這將會(huì)使得查找更快。
結(jié)果
現(xiàn)在我們看一下優(yōu)化后的運(yùn)行結(jié)果:
real 2m19.314s
user 0m36.370s
sys 0m0.940s
第二遍
再次運(yùn)行 gprof 來(lái)分析:
% 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 *)
看起來(lái)以前占用大量運(yùn)行時(shí)的函數(shù)現(xiàn)在已經(jīng)不再是占用運(yùn)行時(shí)的大頭了!我們?cè)囈幌略賰?yōu)化一下呢:用節(jié)點(diǎn)哈希表來(lái)取代節(jié)點(diǎn)樹(shù)。
這次簡(jiǎn)直是個(gè)巨大的進(jìn)步:
real 0m3.269s
user 0m0.830s
sys 0m0.090s
其他 C/C++ 程序分析器
還有其他很多分析器可以使用gprof 的數(shù)據(jù), 例如
KProf (截屏) 和 cgprof。雖然圖形界面的看起來(lái)更舒服,但我個(gè)人認(rèn)為命令行的gprof 使用更方便。
對(duì)其他語(yǔ)言的程序進(jìn)行分析
我們這里介紹了用gprof 來(lái)對(duì)C/C++ 的程序進(jìn)行分析,對(duì)其他語(yǔ)言其實(shí)一樣可以做到: 對(duì) Perl,我們可以用Devel::DProf 模塊。你的程序應(yīng)該以perl -d:DProf mycode.pl來(lái)開(kāi)始,并使用dprofpp來(lái)查看并分析結(jié)果。如果你可以用gcj 來(lái)編譯你的Java 程序,你也可以使用gprof,然而目前還只支持單線程的Java 代碼。
結(jié)論
就像我們已經(jīng)看到的,我們可以使用程序概要分析快速的找到一個(gè)程序里面值得優(yōu)化的地方。在值得優(yōu)化的地方優(yōu)化,我們可以將一個(gè)程序的運(yùn)行時(shí)從 3分36秒 減少到少于 5秒,就像從上面的例子看到的一樣。
新聞熱點(diǎn)
疑難解答