程序的性能分析在判断问题和改进代码上作用很大,Valgrind和gprof是两个很有用的分析工具。

参考资料

  1. 如何在Linux上安装和使用分析工具Gprof
  2. GPROF Tutorial – How to use Linux GNU GCC Profiling Tool
  3. Linux性能评测工具之一:gprof篇
  4. valgrind诊断C/C++内存泄漏
  5. Valgrind详细教程(1) Memcheck
  6. Using Valgrind to Find Memory Leaks and Invalid Memory Use

Valgrind

官方文档

简介

C/C++中内存分配与管理是程序员比较头疼的事情,大型线上业务系统,系统内存泄漏到一定程序,可能会因为分配不到内存而导致宕机,后果很严重。

valgrind工具大件提供了许多调试和性能分析工具,包含包含七个生产质量工具:一个内存错误检测器,两个线程错误检测器,一个缓存和分支预测探查器,一个生成调用图的缓存和分支预测探查器以及两个不同的堆探查器。还包括一个实验性的SimPoint基本块矢量生成器。

这些工具中最流行的称为Memcheck。它可以检测C和C++程序中常见的许多与内存相关的错误,这些错误可能导致崩溃和不可预测的行为。实际上memcheck使用它自己的内存分配代替标准C中的内存分配(malloc和free),因此可以检测出一些异常信息。

Memcheck是Valgrind的王牌,它用于C/C++程序的内存错误检测:

  1. 非法访问内存(堆、栈、内存段错误)
  2. 引用未初始化的变量
  3. 非法释放对内存(重复释放、释放与申请不匹配)
  4. 内存重叠错误
  5. 内存泄露
  6. 错误地申请内存
  7. 内存树的分析

使用

1
2
gcc -g -o main_c main.c
valgrind --tool=memcheck --leak-check=full ./main_c

–tools=memcheck表示使用memcheck工具,valgrind默认的工具也是这个,加不加都可以。

信息

  • HEAP SUMMARY,它表示程序在堆上分配内存的情况,2 allocs表示分配了2次内存,0 frees表示释放了0次,72,714 bytes allocated表示分配了72,714个字节

  • 如果有泄漏,valgrind会报告是哪个位置发生了泄漏(main中cpp第8行)

  • LEAK SUMMARY,表示不同的内存丢失类型

    • definitely loss: 确认丢失,需修复因为在程序运行完的时候,没有指针指向它,指向它的指针在程序中丢失了;
    • indirectly lost: 间接丢失,无须处理,当使用了含有指针成员的类或结构时可能会报这个错误。这类错误无需直接修复,他们总是与”definitely lost”一起出现,只要修复”definitely lost”即可;
    • possibly lost: 可能丢失,需修复,发现了一个指向某块内存中部的指针,而不是指向内存块头部。这种指针一般是原先指向内存块头部,后来移动到了内存块的中部,还有可能该指针和该内存根本就没有关系,检测工具只是怀疑有内存泄漏。
    • still reachable: 可以访问,需修复,未丢失但也未释放。如果程序是正常结束的,那么它可能不会造成程序崩溃。表示泄漏的内存在程序运行完的时候,仍旧有指针指向它,因而,这种内存在程序运行结束之前可以释放。一般情况下valgrind不会报这种泄漏,除非使用了参数 –show-reachable=yes。
    • suppressed:已被解决,无须处理,出现了内存泄露但系统自动处理了;可以无视这类错误。

gprof

官方文档

简介

gprof 是GNU gnu binutils工具之一,默认情况下linux系统当中都带有这个工具。

它实际上只是一个用于读取profile结果文件的工具。它采用混合方法来收集程序的统计信息,使用检测方法,在编译过程中在函数入口处插入计数器用于收集每个函数的被调用情况和被调用次数;也使用采样方法,在运行时按一定间隔去检查程序计数器并在分析时找出程序计数器对应的函数来统计函数占用的时间。

原理

通过在编译和链接程序的时候(使用 -pg 编译和链接选项),gcc 在你应用程序的每个函数中都加入了一个名为mcount ( or “_mcount” , or “__mcount” , 依赖于编译器或操作系统)的函数,也就是说你的应用程序里的每一个函数都会调用mcount, 而mcount 会在内存中保存一张函数调用图,并通过函数调用堆栈的形式查找子函数和父函数的地址。这张调用图也保存了所有与函数相关的调用时间,调用次数等等的所有信息。

使用流程

  1. 在编译和链接时 加上-pg选项。一般我们可以加在 makefile 中。

  2. 执行编译的二进制程序。执行参数和方式同以前。

  3. 在程序运行目录下 生成 gmon.out 文件。如果原来有gmon.out 文件,将会被重写。

  4. 结束进程。这时 gmon.out 会再次被刷新。

  5. 用 gprof 工具分析 gmon.out 文件。

一系列完整的使用命令:

1
2
3
gcc -Wall -std=c99 -pg test_gprof.c -o test_gprof
./test_gprof
gprof test_gprof gmon.out > profile-data.txt

输出信息

Gprof生成的可读输出分为两部分:平面配置文件和调用图。以下是Gprof的手册页面关于这两个部分的信息:

“平面配置文件显示了您的程序在每个功能中花费了多少时间,以及调用多少次功能。如果您只想知道哪些功能可以烧录大部分的周期,那么这里就简明扼要。”

“调用图显示了对于每个函数,调用哪个函数,它调用了哪些其他函数,以及多少次。还有一个估计是在每个函数的子例程中花费了多少时间,这可以建议你所在的地方可能会尝试消除使用大量时间的函数调用。”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Flat profile:

Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
96.43 0.81 0.81 1 810.00 810.00 func3
3.57 0.84 0.03 1 30.00 840.00 func1
0.00 0.84 0.00 1 0.00 810.00 func2
0.00 0.84 0.00 1 0.00 0.00 func4

Call graph (explanation follows)


granularity: each sample hit covers 4 byte(s) for 1.19% of 0.84 seconds

index % time self children called name
0.03 0.81 1/1 main [2]
[1] 100.0 0.03 0.81 1 func1 [1]
0.00 0.81 1/1 func2 [3]
-----------------------------------------------
<spontaneous>
[2] 100.0 0.00 0.84 main [2]
0.03 0.81 1/1 func1 [1]
0.00 0.00 1/1 func4 [5]
-----------------------------------------------
0.00 0.81 1/1 func1 [1]
[3] 96.4 0.00 0.81 1 func2 [3]
0.81 0.00 1/1 func3 [4]
-----------------------------------------------
0.81 0.00 1/1 func2 [3]
[4] 96.4 0.81 0.00 1 func3 [4]
-----------------------------------------------
0.00 0.00 1/1 main [2]
[5] 0.0 0.00 0.00 1 func4 [5]

img

留言

⬆︎TOP