有了函數的調用關係,這會讓開發人員大大提高工作效率,不用費心地去一點點找出程序的運行流程,這對小程序來說可能效果不是很明顯,但對於有幾萬,幾十萬代碼量的工程來說,效率是毋庸置疑的!而且這個功能對於維護舊代碼或者是分析Open Source來說那是相當誘人的,有了調用圖,對程序的運行框架也就有了一個大體瞭解,知道了程序的“骨架“,分析它也就不會再那麼茫然,尤其是對自己不熟悉的代碼和Open Source。費話不多說了,讓我們開始我們的分析之旅吧!
Gprof 實現原理:
通過在編譯和鏈接你的程序的時候(使用 -pg 編譯和鏈接選項),gcc 在你應用程序的每個函數中都加入了一個名爲mcount ( or “_mcount” , or “__mcount” , 依賴於編譯器或操作系統)的函數,也就是說你的應用程序裏的每一個函數都會調用mcount, 而mcount 會在內存中保存一張函數調用圖,並通過函數調用堆棧的形式查找子函數和父函數的地址。這張調用圖也保存了所有與函數相關的調用時間,調用次數等等的所有信息。
Gprof基本用法:
1. 使用 -pg 編譯和鏈接你的應用程序。
2. 執行你的應用程序使之生成供gprof 分析的數據。
3. 使用gprof 程序分析你的應用程序生成的數據。
Gprof 簡單使用:
讓我們簡單的舉個例子來看看Gprof是如何使用的。
1.打開linux終端。新建一個test.c文件,並生用-pg 編譯和鏈接該文件。 test.c 文件內容如下:
#include "stdio.h" #include "stdlib.h" void a(){ printf("\t\t+---call a() function\n"); } void c(){ printf("\t\t+---call c() function\n"); } int b(){ printf("\t+--- call b() function\n"); a(); c(); return 0; } int main(){ printf(" main() function()\n"); b(); } |
命令行裏面輸入下面命令,沒加-c選項,gcc 會默認進行編譯並鏈接生成a.out:
[linux /home/test]$gcc -pg test.c |
如果沒有編譯錯誤,gcc會在當前目錄下生成一個a.out文件,當然你也可以使用 –o 選項給生成的文件起一個別的名字,像 gcc –pg test.c –o test , 則gcc會生成一個名爲test的可執行文件,在命令行下輸入[linux /home/test]$./test , 就可以執行該程序了,記住一定要加上 ./ 否則程序看上去可能是執行,可是什麼輸出都沒有。
2.執行你的應用程序使之生成供gprof 分析的數據。 命令行裏面輸入:
[linux /home/test]$a.out main() function() +--- call b() function +---call a() function +---call c() function [linux /home/test]$ |
3.使用gprof 程序分析你的應用程序生成的數據。
命令行裏面輸入:
[linux /home/test]$ gprof -b a.out gmon.out | less |
% cumulative self self total time seconds seconds calls Ts/call Ts/call name 0.00 0.00 0.00 1 0.00 0.00 a 0.00 0.00 0.00 1 0.00 0.00 b 0.00 0.00 0.00 1 0.00 0.00 c Call graph granularity: each sample hit covers 4 byte(s) no time propagated index % time self children called name 0.00 0.00 1/1 b [2] [1] 0.0 0.00 0.00 1 a [1] ----------------------------------------------- 0.00 0.00 1/1 main [10] [2] 0.0 0.00 0.00 1 b [2] 0.00 0.00 1/1 a [1] 0.00 0.00 1/1 c [3] ----------------------------------------------- 0.00 0.00 1/1 b [2] [3] 0.0 0.00 0.00 1 c [3] |
從上面的輸出我們能明顯的看出來,main 調用了 b 函數, 而b 函數分別調用了a 和 c 函數。由於我們的函數只是簡單的輸出了一個字串,故每個函數的消耗時間都是0 秒。
gprof產生的信息解釋如下:
gprof產生的信息解釋 |
-b不再輸出統計圖表中每個字段的詳細描述。
-p 只輸出函數的調用圖(Call graph 的那部分信息)。
-q 只輸出函數的時間消耗列表。
-E Name不再輸出函數Name 及其子函數的調用圖,此標誌類似於 -e 標誌,但它在總時間和百分比時間的計算中排除了由函數Name 及其子函數所用的時間。
-e Name 不再輸出函數Name 及其子函數的調用圖(除非它們有未被限制的其它父函數)。可以給定多個 -e 標誌。一個 -e 標誌只能指定一個函數。
-F Name 輸出函數Name 及其子函數的調用圖,它類似於 -f 標誌,但它在總時間和百分比時間計算中僅使用所打印的例程的時間。可以指定多個 -F 標誌。一個 -F 標誌只能指定一個函數。-F 標誌覆蓋 -E 標誌。
-f Name輸出函數Name 及其子函數的調用圖。可以指定多個 -f 標誌。一個 -f 標誌只能指定一個函數。
-z 顯示使用次數爲零的例程(按照調用計數和累積時間計算)。
到這爲止你可能對gprof 有了一個比較感性的認識了,你可能會問如何用它去分析一個真正的Open Source 呢!下面就讓我們去用gprof去分析一個Open Source,看看如何去在真實的環境中使用它。
使用Gprof 分析 Cflow開源項目
CFlow 是程序流程分析工具,該工具可以通過分析C源代碼,產生程序調用圖!有點跟Gprof差不多,不過CFlow是通過源代碼進行的靜態分析並且 不能分析C++ 程序,你可以到http://www.gnu.org/software/cflow/去下載源代碼。
假設你已經下載了該源代碼(cflow-1.1.tar.gz),並把它放置在/home目錄下,讓我們看看如何在這個應用上使用gprof。
1. 使用 -pg 編譯和鏈接該應用程序,請輸入下列命令。
[linux /home/]tar zxvf cflow-1.1.tar.gz [linux /home/cflow-1.1/src]$./configure [linux /home]$make CFLAGS=-pg LDFLAGS=-pg |
2. 運行cflow 程序使之生成gmon.out 文件供gprof使用。
[linux /home/cflow-1.1/src]$cflow parser.c |
3. 使用gprof分析程序
[linux /home/cflow-1.1/src]$gprof -b cflow gmon.out | less
恭喜你,不出意外你會在屏幕上看到gprof的輸出,函數消耗時間和函數調用圖,下面是我從我的輸出中摘抄出來的一小段。
% cumulative self self total time seconds seconds calls Ts/call Ts/call name 0.00 0.00 0.00 118262 0.00 0.00 include_symbol 0.00 0.00 0.00 92896 0.00 0.00 is_printable 0.00 0.00 0.00 28704 0.00 0.00 set_level_mark 0.00 0.00 0.00 28703 0.00 0.00 is_last 0.00 0.00 0.00 19615 0.00 0.00 auto_processor 0.00 0.00 0.00 15494 0.00 0.00 gnu_output_handler 0.00 0.00 0.00 12286 0.00 0.00 delete_parm_processor 0.00 0.00 0.00 7728 0.00 0.00 newline 0.00 0.00 0.00 7728 0.00 0.00 print_function_name 0.00 0.00 0.00 7728 0.00 0.00 print_level 。。。。。。 。。。。。。 Call graph granularity: each sample hit covers 4 byte(s) no time propagated index % time self children called name [1] 0.0 0.00 0.00 79+855 [1] 0.00 0.00 166 dcl [52] 0.00 0.00 163 parse_dcl [53] 0.00 0.00 150 dirdcl [56] 0.00 0.00 129 parse_declaration [63] 0.00 0.00 98 parse_variable_declaration [66] 0.00 0.00 63 maybe_parm_list [69] 0.00 0.00 63 parse_function_declaration [70] 0.00 0.00 39 func_body [74] 。。。。。。 。。。。。。 |
通過分析%time你就知道了那個函數消耗的時間最多,你可以根據這個輸出信息做有目的的優化,不過cflow執行的速度是在是太快了,以至%time都是0 (消耗時間是以秒爲單位進行統計的)。
生成圖形化的函數調用圖
1.Graphviz 工具
看到這裏你也可能覺得上面的函數調用圖實在是不方便察看,也看不出來一個程序調用的整體框架。沒有關係,我再介紹一個有用的工具給你,使用 Graphviz,Graphviz or Graph Visualization 是由 AT&T 開發的一個開源的圖形可視化工具。它提供了多種畫圖能力,但是我們重點關注的是它使用 Dot 語言直連圖的能力。在這裏,將簡單介紹如何使用 Dot 來創建一個圖形,並展示如何將分析數據轉換成 Graphviz 可以使用的規範, Dot 使用的圖形規範。
使用 Dot 語言,你可以指定三種對象:圖、節點和邊。爲了讓你理解這些對象的含義,我將構建一個例子來展示這些元素的用法。
下圖給出了一個簡單的定向圖(directed graph),其中包含 3 個節點。第一行聲明這個圖爲 G,並且聲明瞭該圖的類型(digraph)。接下來的三行代碼用於創建該圖的節點,這些節點分別名爲 node1、node2 和 node3。節點是在它們的名字出現在圖規範中時創建的。邊是在在兩個節點使用邊操作(->)連接在一起時創建的,如第 6 行到第 8 行所示。我還對邊使用了一個可選的屬性 label,用它來表示邊在圖中的名稱。最後,在第 9 行完成對該圖規範的定義。
使用 Dot 符號表示的示例圖(test.dot)
1: digraph G { 2: node1; 3: node2; 4: node3; 5: 6: node1 -> node2 [label="edge_1_2"]; 7: node1 -> node3 [label="edge_1_3"]; 8: node2 -> node3 [label="edge_2_3"]; 9: } |
清單 6. 使用 Dot 來創建 JPG 映像
[linux /home]$ dot -Tjpg test.dot -o test.jpg
在這段代碼中,我告訴 Dot 使用 test.dot 圖形規範,並生成一個 JPG 圖像,將其保存在文件 test.jpg 中。所生成的圖像如圖1所示。在此處,我使用了 JPG 格式,但是 Dot 工具也可以支持其他格式,其中包括 GIF、PNG 和 postscript等等。
圖 1. Dot 創建的示例圖 |
Dot 語言還可以支持其他一些選項,包括外形、顏色和很多屬性。有興趣可以查看graphviz相關文檔。
2.從gprof的輸出中提取調用圖信息,產生可供Graphviz使用的dot文件。
這樣的腳本有人已經實現了,我們只要下載一個現成的就可以了,首先從http://www.ioplex.com/~miallen/ 網站下載一個mkgraph腳本。解壓該腳本到包含gmon.out文件的目錄下。使用mkgraph0.sh產生調用的jpg圖像文件。例如:使用上面的例子,生成cflow的調用圖。
[linux /home/cflow-1.1/src]$ mkgraph0.sh cflow gmon.out