1.3.7 gprof:性能分析工具
gprof是GNU profile工具,可以运行于linux、AIX、Sun等操作系统进行C、C++、Pascal、Fortran程序的性能分析,用于程序的性能优化以及程序瓶颈问题的查找和解决。通过分析应用程序运行时产生的 'flat profile',可以得到每个函数的调用次数,每个函数消耗的处理器时间,也可以得到函数的 '调用关系图' ,包括函数调用的层次关系,每个函数调用花费了多少时间。
Gprof具有以下优缺点:
需要编译选项支持:
调试多线程程序只能统计主线程的信息(所以不能用于 kingbase)。
例如:gcc -pg -o test test.cpp ,编译器会自动在目标代码中插入用于性能测试的代码片断,这些代码在程序运行时采集并记录函数的调用关系和调用次数,并记录函数自身执行时间和被调用函数的执行时间。
执行编译后的可执行程序,如:./test。该步骤运行程序的时间会稍慢于正常编译的可执行程序的运行时间。程序运行结束后,会在程序所在路径下生成一个缺省文件名为 gmon.out 的文件,这个文件就是记录程序运行的性能、调用关系、调用次数等信息的数据文件。
使用 gprof 命令来分析记录程序运行信息的 gmon.out 文件,如:gprof test gmon.out 则可以在显示器上看到函数调用相关的统计、分析信息。上述信息也可以采用 gprof test gmon.out > gprofresult.txt 重定向到文本文件以便于后续分析。
使用 gcc/cc 或 g++ 编译和链接时需要加入 -pg 选项;
使用 ld 链接时需要用 /lib/gcrt0.o 代替 crt0.o 作为第一个 input 文件
如果要调试 libc 库需要使用 -lc_p代替 -lc 参数
GNU工具,人手一个;
混合方法采集信息。
优点:
缺点:
命令行选项如下:
选项 |
描述 |
-b |
不再输出统计图表中每个字段的详细描述。 |
-q |
只输出函数的调用图(Call graph的那部分信息)。 |
-p |
只输出函数的时间消耗列表。 |
-e Name |
不再输出函数 Name 及其子函数的调用图(除非它们有未被限制的其它父函数)。可以给定多个 -e 标志。一个 -e 标志只能指定一个函数。 |
-E Name |
不再输出函数 Name 及其子函数的调用图,此标志类似于 -e 标志,但它在总时间和百分比时间的计算中排除了由函数 Name 及其子函数所用的时间。 |
-f Name |
输出函数 Name 及其子函数的调用图。可以指定多个 -f 标志。一个 -f 标志只能指定一个函数。 |
-F Name |
输出函数 Name 及其子函数的调用图,它类似于 -f 标志,但它在总时间和百分比时间计算中仅使用所打印的例程的时间。可以指定多个 -F 标志。一个 -F 标志只能指定一个函数。-F 标志覆盖 -E 标志。 |
-z |
显示使用次数为零的例程(按照调用计数和累积时间计算)。 |
例子:
1 #include
2 #include
3 int a(void)
4 {
5 int i=0,g=0;
6 while(i++ < 100000)
7 {
8 g+=i;
9 }
10
11 return g;
12 }
13
14 int b(void)
15 {
16 int i=0,g=0;
17
18 while(i++ < 400000)
19 {
20 g +=i;
21 }
22
23 return g;
24 }
25
26 int main(int argc, char** argv)
27 {
28 int iterations;
29
30 if(argc != 2)
31 {
32 printf('Usage %s
33 exit(-1);
34 }
35 else
36 iterations = atoi(argv[1]);
37 printf('No of iterations = %dn', iterations);
38
39 while(iterations--)
40 {
41 a();
42 b();
43 }
44 }
应用程序包括两个函数:a 和 b,它们通过运行不同次数的循环来消耗不同的CPU时间。
main 函数中采用了一个循环来反复调用这两个函数。函数 b 中循环的次数是 a 函数的 4 倍,因此我们期望通过 gprof 的分析结果可以观察到大概有 20% 的时间花在了 a 函数中,而 80% 的时间花在了 b 函数中。
编译程序:gcc test.c -pg -o test -O2 -lc
运行并传入参数:./test 50000
程序运行完之后,会在目录下生成一个 gmon.out 文件:
使用 gprof 命令分析分析 gmon.out 文件gprof test gmon.out -p
程序运行时间太短,所以 gprof 无效,若是大程序即可使用此来分析。
上面那些参数得含义如下:
名称 |
含义 |
%time |
函数以及衍生函数(函数内部再次调用的子函数)所占的总运行时间的百分比 |
cumulative seconds |
函数累计执行的时间 |
self seconds |
函数执行占用的时间 |
calls |
函数的调用次数 |
self ms/call |
每一次调用函数花费的时间microseconds,不包括衍生函数的运行时间 |
total ms/call |
每一次调用函数花费的时间microseconds,包括衍生函数的运行时间 |
name |
函数名称 |
1.3.8 ld:GNU 链接器
ld 是 GNU 工具链中的一个软件,主要用于将 obj 文件链接成可执行文件。同时可以使用自己的脚本来控制 ld 的行为,可以通过 -T 选项选择自己的脚本而不是默认的。
选项 |
描述 |
-static |
静态链接 |
-l |
指定链接某个库 |
-e name |
指定 name 为程序入口 |
-r |
合并目标文件,不进行最终链接 |
-L |
指定链接时查找路径,多个路径之间用冒号隔开 |
-M |
将链接时的符号和地址输出成一个映射文件 |
-o |
指定输出的文件名 |
-s |
清除输出文件中的符号信息 |
-shared |
链接器生成一个 Linux 上使用的动态库 |
-S |
清除输出文件中的调试信息 |
-T file> |
指定链接脚本文件 |
-Ttext |
指定 text 段的地址 |
-version-script |
指定符号版本脚本文件 |
-soname |
指定输出动态库的 SONAME |
-export-dynamic |
将全局符号全部导出 |
-verbose |
链接时输出详细信息 |
-rpath |
指定链接时库查找路径 |
--help |
查看链接器的帮助信息 |
1.3.9 libbfd:二进制文件描述器
libbfd 工具不会在安装 binutils 的时候自动安装,需要在 binutils 安装包的 bfd 文件夹下单独安装。
在安装完 binutils 工具之后就可以看到此工具。安装完成后,会生成如下文件:
/usr/local/include/bfd.h
/usr/local/lib/libbfd.a
可以利用此工具获取 elf 可执行文件的 section(节) 及 symbol(符号) 信息。
使用此工具需要注意的地方:
头文件包含
采用GNU autotools的项目,在编译前一般都会执行一下 configure 脚本,生成 Makefile 及 config.h文 件。
对于没有使用 GNU autotools 的应用,可以采用如下格式得到 config.h 文件,这个文件的内容,相当于是使用 GNU autotools 开发一个 hello world 项目而得到的 config.h,下面就是 config.h 文件的模板
程序使用bfd,需要包含bfd.h头文件。但是,在包含 bfd.h 之前,还需要包含 config.h。即代码中需要有如下形式的文件包含:
#include 'config.h'
#includeconfig.h 不是系统的头文件,也不是bfd库的头文件,而是应用程序自己的头文件。
1 /* config.h. Generated from config.h.in by configure. */
2 /* config.h.in. Generated from configure.ac by autoheader. */
3
4 /* Name of package */
5 #define PACKAGE 'hello'
6
7 /* Define to the address where bug reports for this package should be sent. */
8 #define PACKAGE_BUGREPORT 'bug-report@address'
9
10 /* Define to the full name of this package. */
11 #define PACKAGE_NAME 'hello'
12
13 /* Define to the full name and version of this package. */
14 #define PACKAGE_STRING 'hello 1.0'
15
16 /* Define to the one symbol short name of this package. */
17 #define PACKAGE_TARNAME 'hello'
18
19 /* Define to the home page for this package. */
20 #define PACKAGE_URL ''
21
22 /* Define to the version of this package. */
23 #define PACKAGE_VERSION '1.0'
24
25 /* Version number of package */
26 #define VERSION '1.0'
链接
链接的时候需要带上这几个库:bfd iberty dl z
例如,假设 hello.c 是一个完整的使用 bfd 库的程序,则他的编译方法如:gcc hello.c -lbfd -liberty -ldl -lz
例子如下:
1 #include
2 #include
3 #include 'config.h'
4 #include
5 #include
6 #include
7 #include
8 #include
9
10 /*
11 这里定义 3 个 static 变量,并把他们放到一个单独的 section 中。
12 后面,我们通过 bfd 找出这个 section,并得到这 3 个变量的内容。
13 同时,我们还通过符号查找操作,找到 a_haha 这个 static 变量的信息。
14 */
15 static uint64_t a_haha __attribute__((section ('my_test_sec'))) =3;
16 static uint64_t b __attribute__((section ('my_test_sec'))) =7;
17 static uint64_t c __attribute__((section ('my_test_sec'))) =8;
18
19 /* 获取当前进程自己的elf文件路径 */
20 int get_self_path(char *buf, int buf_len)
21 {
22 int ret = readlink('/proc/self/exe', buf, buf_len);
23 buf[ret]='';
24 return ret;
25 }
26
27 void section_proc(bfd *abfd, asection *sect, PTR obj)
28 {
29 if (strcmp(sect->name, 'my_test_sec')==0)
30 printf('section %s existsn', sect->name);
31 }
32
33 void search_a_given_symbol(bfd *ibfd, const char *name)
34 {
35 long storage_needed;
36 asymbol **symbol_table;
37 long number_of_symbols;
38 long i;
39 symbol_info symbolinfo ;
40
41 storage_needed = bfd_get_symtab_upper_bound(ibfd);
42
43 symbol_table = (void *)(unsigned long)malloc(storage_needed);
44 number_of_symbols = bfd_canonicalize_symtab (ibfd, symbol_table);
45
46 printf('Scanning %ld symbolsn', number_of_symbols);
47 for(i=0;i
49 if (symbol_table[i]->section==NULL) continue;
50
51 bfd_symbol_info(symbol_table[i], &symbolinfo);
52 if (strcmp(name, symbolinfo.name)) continue;
53
54 printf('Section %s ', symbol_table[i]->section->name);
55 printf('Symbol '%s' value 0x%lxn', symbolinfo.name, symbolinfo.value);
56 }
57 }
58
59 int main()
60 {
61 char our_self_path[1024];
62 bfd *ibfd;
63 char **matching;
64
65 asection *psection;
66
67 bfd_init();
68
69 get_self_path(our_self_path, sizeof(our_self_path));
70 printf('our elf file path:%sn', our_self_path);
71
72 ibfd = bfd_openr(our_self_path, NULL);
73 bfd_check_format_matches(ibfd, bfd_object, &matching);
74
75 printf('number of sections = %dn', bfd_count_sections(ibfd));
76
77 /* 遍历所有 section,让 section_proc 对每一个 section 进行处理 */
78 bfd_map_over_sections(ibfd, section_proc, NULL);
79
80 /* 查找特定名称的 section ,打印出其信息 */
81 psection = bfd_get_section_by_name(ibfd, 'my_test_sec');
82 printf('section name=%s; start_address=0x%lx; size=%ldn', psection->name, psection->vma, psection->size);
83
84 /* 打印出my_test_sec section中的 3 个 uint64_t 变量的值 */
85 {
86 uint64_t *pu64 = (void *) psection->vma;
87 printf('%lu %lu %lu n', pu64[0], pu64[1], pu64[2]);
88 }
89
90 printf('address of a_haha=%pn', &a_haha);
91
92 /* 遍历所有符号,以找出名称为 a_haha 的符号 */
93 search_a_given_symbol(ibfd, 'a_haha');
94 return 0;
95 }
编译:gcc test.c -lbfd -liberty -ldl -lz
执行如下:
1.3.11 libiberty
包含多个 GNU 程序会使用的途径,包括 getopt、obstack、strerror、strtol 和 strtoul。