Profile 工具系列之一:gprof

xiaoxiao2021-02-28  78

先说结论

gprof 在目前来说,在 profile 界并没有明显优势首先是对动态库支持不好其次它基本可以被 gperftools 取代所以没兴趣的就不必往下看了

用法

gcc 编译时带上 -pg 参数,运行程序时就会生成 gmon.out 文件然后用 gprof 命令分析该文件

gcc 的 -pg 参数

You must use this option when compiling the source files you want data about, and you must also use it when linking. 即编译和连接时,都需要-pg参数

具体用法举例:ffmepg

网上有说 ffmpeg 的 config 选项支持 --enable-gprof,不过我再2.8的 ffmpeg 源码上做了实验,说不认识这个选项只能手动加,ffmepg 在 config 时,加入如下三个选项: --extra-cflags=-pg \ --extra-ldflags=-pg \ --extra-cxxflags=-pg \ 在 Windows 上用 MinGW 可以编译出打开了 gprof 的 ffmepg,运行时也能生存 gmon.out不过在用 gprof 分析时报错: gprof ffmpeg.exe gmon.out C:\msys64\mingw32\bin\gprof.exe: file `ffmpeg.exe' has no symbols 转战 Linux 试一下在 Linux 上出现同样问题:加了三个 -pg 编出来的 ffmpeg 可以在运行的同时输出 gmon.out 文件,但是在用 gprof 进行分析的时候报错: gprof: file ffmpeg has no symbols 网上搜了一下,是因为 executable 被 strip 了,还好 ffmpeg 的 config 提供了 --disable-stripping 选项为了保险起见,又加了 --enable-debug=3 选项修改 config 加上上述选项,重新编译。这回 gprof 可以正常运行了。不过新问题是,运行显示的结果,只有寥寥几个 ffmpeg.c 里的函数被统计到了,且耗时几乎都为0。真正耗时的编解码函数(libavcodec.so里的)都没有被统计到。这是因为尽管编译时产生的各种 so 库,比如 libavcodec.so,都已经加了 -pg 了,但运行时实际载入内存的库,并不是这个。运行时载入内存的,是机器上本来就安装好的 libavcodec.so(用 ldd ffmpeg 可以看一下),而之前安装的库,是没有加 -pg 的,所以里面的函数跟踪不到。有两个解决办法: 把新编出来的加了-pg 的库放到系统的库目录里,比如 /usr/lib/ ,替换到原来的库(此方法失败,原因是 gprof 对动态库支持不好,见下文)改用静态库编译 ffmpeg,这样运行 ffmpeg 时就不会去连接操作系统上原来的动态库了,新编译的带了-pg 的二进制函数都被直接放到 ffmpeg 里面了。 改用静态库的配置选项是:--disable-shared --enable-static

关于动态库的支持

参考:http://www.cnblogs.com/lenolix/archive/2010/12/13/1904868.html生成一个可执行程序的时候,gcc(实际上是ld)会把 libc.a/libc.so 连接入可执行文件,这个库负责初始化程序的栈空间等工作。即 libc 会在你业务代码之前运行。加了 -pg 选项,在连接阶段,gcc 会把本应该链接到可执行文件上的 libc.a/libc.so 里的 crt0.o,改成 gcrt0.ogcrt0.o 会在你的main函数之前插入一个函数:__monstartup,该函数用来做 profile 相关的初始化工作,比如分配所需的内存该函数只会对程序的 .text 段进行 profile 的初始化。因为动态链接库的代码并不在主程序的 .text 段,所以是不会被 profile 到的。如果想让动态库也能被 profile,只能修改 libc 的代码,这样做不太好。或者按照网上有些说法,用 libc_p.a/libc_p.so 代替正常的 libc.a/libc.so,即把链接选项 -lc 替换为 -lc_p。因为 libc_p 就是支持了 profile 的 libc不过这么做也有困难,首先不是说有版本的 Linux 上都能安装到 libc_p 的,另外在有些系统上,比如下文的 Ubuntu,加了 -lc_p 也报错,编译不出来可执行程序。

动态库的题外话

首先,动态库也分两种:动态链接和动态加载动态链接是程序运行一开始就把库加载进内存,主程序在调用到库函数的时候才会走到库里的代码。动态加载是指当程序运行到一定地方的时候,用 dlopen 来加载库,并使用其中的代码。也就是说在主程序想使用库函数之前,库文件是没有被加载进内存的。这种方式在使用完了之后,需要用 dlclose 卸载库。

检查是否成功加入了 -pg 选项

如何检查编出来的库/可执行文件是否成功加入了 -pg 选项呢?-pg 的原理是: gcc 在你应用程序的每个函数中都加入了一个名为 mcount ( or “_mcount” , or “__mcount”)的函数,也就是说你的应用程序里的每一个函数都会调用mcount, 而mcount 会在内存中保存一张函数调用图,并通过函数调用堆栈的形式查找子函数和父函数的地址。这张调用图也保存了所有与函数相关的调用时间,调用次数等等的所有信息。所以,想检查编出来的库文件或可执行文件,-pg 选项到底好使了没有,可以查看它的符号表,搜索一下关键字 mcount: nm xxxxxxx | grep mcount

实验1:动态库确实不行

准备源文件:libtestprofiler.cpp libtestprofiler.h main.cpp其中库函数只是单纯的耗时计算,所有源文件内容如下: //libtestprofiler.h extern "C"{ int loopop(); } //libtestprofiler.cpp #include "libtestprofiler.h" extern "C"{ int loopop() // 一个耗时函数 { int n = 0; for(int i = 0; i < 1000000; i++) for(int j = 0; j < 10000; j++) { n |= i%100 + j/100; } return n; } } //main.cpp #include <iostream> #include "libtestprofiler.h" using namespace std; int main(int argc,char** argv) { cout << "loopop: " << loopop() << endl; return 1; } 编译库(加 -pg): g++ --shared -fPIC -pg -o libtestprofiler.so libtestprofiler.cpp 编译主程序(加 -pg) g++ -pg -o main main.cpp -ltestprofiler -L. 拷贝库到 /lib/: sudo cp libtestprofiler.so /lib/ 运行主程序: ./main 运行结束后生成了 gmon.out,用 gprof 分析之: gprof main 发现什么也没统计到,所有函数耗时都是0看一下库文件里是否加入了 mcount: nm libtestprofiler.so | grep mcount 结果是加入了的,能看到输出:U mcount@@GLIBC_2.2.5,不过还是采集不到动态库里函数的 profile 信息。

实验2:让动态库也行(实验失败)

Ubuntu 默认是没有 libc_p.a/libc_p.so 的,需要安装 libc6-prof: sudo apt-get install libc6-prof 源文件同上,库的编译命令不变在主程序的编译命令里加入 –lc_p 选项。 g++ -pg -o main main.cpp -ltestprofiler -L. -lc_p 以上命令编译报错,google 了半天没有找到答案,这个实验暂时不做了。

其他

gcc 命令加了个 xxxx.exp 导致编出来的动态库文件符号表里并没有 mcount,虽然我也加了 -pg 选项。.exp 文件里只写了 VERSION 有关信息: VERSION{….},为什么我也不知道,但去掉这个就好了。

参考文档

http://blog.csdn.net/stanjiang2010/article/details/5655143http://www.cnblogs.com/lenolix/archive/2010/12/13/1904868.html
转载请注明原文地址: https://www.6miu.com/read-71053.html

最新回复(0)