C++代码使用 gperftools 工具进行性能分析

前言

一直想用 gperftools 做一下性能方面的尝试,之前一直忙着开发,目前已经到了后期,忙里抽闲亲自操作一遍,从安装到分析做个简单的记录,以便后续拿来直接用。

gperftools 是什么

gperftools 是Google开发的用来进行代码性能分析工具,其实他是一系列高性能多线程 malloc() 实现的集合,同时添加了一些精巧的性能分析工具。

gperftools

使用gperftools工具可以通过采样的方式生成上面这种图形化的代码性能分析结果,便于我们分析程序性能瓶颈。

使用方法

C++程序按照代码插桩的方式引入了gperftools工具,不过这个工具需要单独安装,为了生成图形化的分析结果,还需要安装一些依赖库,下面简述以下使用功能步骤。

安装工具

  1. 安装编译所需基础软件

    1
    sudo apt install autoconf automake libtool
  2. 安装graphviz,用于图形化显示分析结果

    1
    sudo apt install graphviz
  3. 安装libunwind, 这个库提供了可用于分析程序调用栈的 API

    1
    2
    3
    4
    5
    6
    7
    8
    9
    cd /tmp
    wget https://github.com/libunwind/libunwind/releases/download/v1.6.2/libunwind-1.6.2.tar.gz
    tar -zxvf libunwind-1.6.2.tar.gz
    cd libunwind-1.6.2
    ./configure
    make -j4
    sudo make install
    cd /tmp
    rm -rf libunwind-1.6.2.tar.gz libunwind-1.6.2
  4. 安装gperftools

    1
    2
    3
    4
    5
    6
    7
    8
    9
    cd /tmp
    wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.10/gperftools-2.10.tar.gz
    tar -zxvf gperftools-2.10.tar.gz
    cd gperftools-2.10
    ./configure
    make -j4
    sudo make install
    cd ~
    rm -rf gperftools-2.10.tar.gz gperftools-2.10
  5. 刷新动态装入程序所需的链接和缓存文件

    1
    sudo ldconfig

代码插桩引入工具

代码修改

主要在源程序中引入头文件,并且在待测试逻辑前后添加启动分析和结束分析的语句就行了,对于服务类程序,因为要一直运行,可以通过kill信号通知来开启和关闭性能分析。

关键代码

1
2
3
4
5
6
#include <gperftools/profiler.h> // 引入头文件
...
ProfilerStart("cpp_demo.prof"); // 启动分析
...
ProfilerStop(); // 结束分析
...

完整示例

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
#include <iostream>
#include <gperftools/profiler.h>

static void sig(int sig) // kill -10 pid to trigger
{
static bool b = false;
if (!b)
ProfilerStart("cpp_demo.prof");
else
ProfilerStop();

b= !b;
}

int main(int argc, char* argv[])
{
signal(SIGUSR1, sig);

while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
//..
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count() << std::endl;
}

return 0;
}

编译链接

编译时我们需要将 profiler 库和 libunwind 库链接到可执行程序,如果使用 cmake来构建,那么 CMakeLists 文件中的语句为:

1
target_link_libraries(${PROJECT_NAME} profiler unwind)

启动分析程序

  1. 正常启动游戏服务器,通过ps命令查找到要分析的进程id,比如查找到demoserver的进程是 7217

    1
    2
    $ ps -ef | grep demoserver
    demo 7217 1 22 21:51 ? 00:00:18 ./demoserver-d
  2. 通过kill命令传递自定义信号10的方式启动和关闭分析程序,第一次运行命令是启动,第二次运行相同的命令是关闭,两次命令之间是分析的时间段

    1
    $ kill -10 7217
  3. 关闭分析程序之后,会在可执行程序所在目录生成 cpp_demo.porf 文件,可以使用下面命令将结果图形化

    1
    2
    3
    4
    $ pprof --pdf demoserver cpp_demo.prof > demoserver.pdf
    Using local file demoserver.
    Using local file cpp_demo.prof.
    Dropping nodes with <= 1 samples; edges with <= 0 abs(samples)
  4. 最终生成的 demoserver.pdf 文件就是我们要用的分析结果,如文章开头所示。

数据分析

上面提到了生成pdf图,其实可以生成txt文本的,只要修改生成选项就可以,比如像这样:

1
2
3
4
5
6
7
8
9
# pprof --text demoserver cpp_demo.prof
Using local file demoserver.
Using local file cpp_demo.prof.
Total: 13 samples
3 21.4% 21.4% 3 21.4% SpinLock::Unlock (inline)
3 21.4% 42.9% 3 21.4% __GI_madvise
2 14.3% 57.1% 2 14.3% SpinLock::Lock (inline)
1 7.1% 64.3% 1 7.1% TCMalloc_PageMap2::get (inline)
...

上述文本数据每行包含6列数据,依次为:

  1. 分析样本数量(不包含其他函数调用)
  2. 分析样本百分比(不包含其他函数调用)
  3. 目前为止的分析样本百分比(不包含其他函数调用)
  4. 分析样本数量(包含其他函数调用)
  5. 分析样本百分比(包含其他函数调用)
  6. 函数名(或者类名+方法名)

样本数量相当于消耗的CPU时间,整个函数消耗的CPU时间相当于包括函数内部其他函数调用所消耗的CPU时间,如果是分析最上面的pdf图,每个节点代表一个函数,包含2~3行数据:

  1. 函数名(或者类名+方法名)
  2. 不包含内部函数调用的样本数 (百分比)
  3. of 包含内部函数调用的样本数 (百分比) #如果没有内部调用函数则不显示

    总结

  • gperftools 是可以通过采样的方式进行代码性能分析工具,可生成图形化结果便于我们分析程序性能瓶颈
  • 待分析程序中引入gperftools非常方便,但是需要单独安装这个工具
  • 程序引入时只需要添加头文件,在目标位置插入 ProfilerStart("cpp_demo.prof");ProfilerStop(); 语句即可
  • 对于服务类程序通常不会直接结束,可以通过 kill 命令传递信号的方式来启动和关闭分析程序
==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

可能终于有一天 刚好遇见爱情
可能永远在路上 有人奋斗前行
可能一切的可能 相信才有可能
可能拥有过梦想 才能叫做青春

2023-4-19 23:22:53

Albert Shi wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客