C++分析程序各模块耗时-perf火焰图

Reference:

  1. Perf Wiki
  2. 【性能】perf + 火焰图分析软件性能瓶颈
  3. 【火焰图🔥】Linux C/C++性能优化分析工具Perf使用教程
  4. Linux性能分析工具-perf并生成火焰图

perf: Linux profiling with performance counters(带有性能计数器的Linux分析)

1. 简介

perf 是一个非常实用且深入的性能分析工具,适用于从底层硬件交互到上层应用程序逻辑的全方位性能剖析。

perf 工具的设计目的是为了帮助开发者和系统管理员分析应用程序以及内核本身的性能,寻找潜在的性能瓶颈,并据此进行针对性的优化。

perf 主要特性包括以下几个方面:

  1. 函数级和指令级热点分析:perf 可以通过采样分析,精确到函数级别或甚至指令级别来发现CPU占用率高的热点代码段;
  2. 性能计数器:利用Linux内核的perf_events子系统,它能够监控和记录各种硬件事件,如CPU cycles、指令执行、缓存未命中的次数、分支预测失败等,还可以监控软件事件,如系统调用、页错误等;
  3. CPU性能监测:监控各个进程或线程的CPU使用率,识别哪些函数或代码路径是CPU消耗大户(和第一条好像没区别);
  4. 缓存行为分析:分析内存访问模式,包括L1、L2、L3缓存的命中和未命中的情况,有助于优化内存访问策略;
  5. 系统调用和事件跟踪:记录和分析程序运行过程中的系统调用,以及相关的事件流;
  6. 调用图生成:创建函数调用图,可视化地展示函数间的调用关系,方便理解程序执行流程及其开销;
  7. 动态跟踪:支持动态跟踪技术,可以实时捕获和分析程序运行时的行为;
  8. 跨进程和跨线程分析:不仅能分析单个进程的性能,还能处理多进程间的数据关联,提供全面的系统级视图;
  9. 兼容性:由于其直接集成在内核中,perf 具备良好的硬件兼容性,能够充分利用不同架构CPU的性能监控单元(PMU)。

2. 工作原理

perf 工作原理的核心在于对 Linux 内核性能事件的支持和利用。以下是 perf 工具的基本工作原理概述:

  1. 硬件事件(Hardware Events):perf 能够利用CPU内部的 Performance Monitoring Units (PMUs)。PMUs是一组硬件寄存器,能够追踪和记录特定的微架构事件,如指令执行次数、缓存未命中、分支预测失败等。通过编程这些寄存器,perf可以设置特定事件的阈值,在达到一定次数后触发中断,收集此时的上下文信息,如CPU寄存器状态、PC指针(指向当前正在执行的指令地址)、进程ID、线程ID等。
  2. 软件事件(Software Events):对于软件级别的事件,perf 利用内核中的计数器和 tracepoints。内核计数器可以追踪系统级事件,如上下文切换、页错误、任务调度等。Tracepoints 是内核中预定义的静态探针点,允许在特定位置插入额外的追踪代码,以便在关键内核操作发生时收集信息。
  3. 采样(Sampling):perf 工具主要采用采样法进行性能分析。它可以根据设置的事件类型和频率定期或者在满足条件时触发采样,每次采样都会记录下当时CPU正在执行的指令或函数信息。通过大量的采样数据,可以得出热点函数或代码段,从而找出可能导致性能瓶颈的部分。
  4. 记录与报告(Recording and Reporting):perf 可以实时或事后分析记录的数据。它可以创建统计数据报告,展示CPU使用率、上下文切换、内存访问统计等信息,还可以生成调用图(火焰图)以直观地显示函数调用层级和耗时情况。
  5. 跟踪(Tracing):除了采样之外,perf 还提供了更复杂的跟踪能力,允许持续记录系统的活动,包括系统调用、函数入口退出、irq处理等,这对于分析系统行为和优化系统响应时间非常有用。

总之,perf 集成了多种性能分析手段,通过对硬件和软件事件的监控,结合采样和跟踪技术,实现了对Linux系统和应用程序的深度性能剖析。

2. 安装

sudo apt install linux-tools-common
// 下面步骤根据 Linux 内核来。比如查看 uname -a 得到内核版本,根据相应版本修改下面指令
sudo apt install linux-tools-5.15.0-101-generic

  • 查看 perf 版本

    perf --version

3. perf 相关命令

在这里插入图片描述最常用的perf命令及其功能理解如下:

annotate        读取perf.data(由perf record生成)并结合源代码展示详细的性能分析结果,包括CPU执行热点、函数调用栈等信息。
archive         使用perf.data文件中找到的带构建标识符的对象文件创建归档文件,便于后续对这些对象文件进行调试或者分析
bench           通用基准测试套件框架,允许用户定义和运行多种基准测试场景,用于评估系统在不同条件下的性能表现。
buildid-cache   管理perf用来关联二进制文件与其符号表信息的构建ID缓存,可以添加、删除或查看缓存内容。
buildid-list    列出perf.data文件中的构建标识符
c2c             共享数据C2C/HITM分析器:针对共享数据缓存一致性(Cache-to-Cache)和高速缓存命中(Hit in Translation)进行分析的工具,帮助诊断多核心间的缓存交互问题。
config          读取和设置perf配置文件中的变量,用于个性化perf的行为或指定默认参数。
daemon          在后台运行记录会话,适用于长期监控系统的性能情况。
data            提供一系列与perf.data文件相关联的操作,如检查文件内容、转换格式等。
diff            比较两个或多个perf.data文件,分析并展示它们之间的性能差异,常用于对比不同条件下系统的性能变化。
evlist          列出perf.data文件中的事件名称,这些事件可能包括硬件性能计数器事件、软件事件、tracepoint事件等。
ftrace          内核ftrace功能的简单包装器,允许实时追踪和分析内核函数调用路径。
inject          过滤器,用于向事件流中添加附加信息,增强事件的数据量和丰富度,便于更加深入地分析性能问题
iostat          显示I/O性能指标,如块设备读写速率、I/O操作延时等
kallsyms        在运行中的内核中搜索符号
kmem            用于跟踪/测量内核内存属性的工具,如分配、释放、碎片率等
kvm             用于跟踪/测量KVM虚拟机操作系统性能的工具
list            列出所有符号事件类型
lock            分析系统中锁的获取和释放行为,包括锁竞争、等待时间等,有助于发现潜在的并发瓶颈
mem             对内存访问模式进行分析,包括页错误、缓存未命中的次数、内存带宽使用等。
record          执行命令并将其性能概要记录到perf.data中
report          读取perf.data(由perf record创建)并显示概要
sched           用于跟踪/测量调度器属性(延迟)的工具,从而优化进程调度策略。
script          读取perf.data(由perf record创建)并显示跟踪输出
stat            执行命令并收集性能计数器统计信息
test            运行内置的一系列sanity测试,确保perf工具自身正确性和稳定性。
timechart       工具用于可视化工作负载期间的系统整体行为
top             系统性能分析工具,类似于Linux的top命令,但专注于性能分析,显示正在运行进程的实时性能统计数据。
version         显示perf二进制文件的版本信息
probe           定义新的动态跟踪点,使得perf能够追踪和记录自定义的内核函数或模块行为。
trace           类似strace的工具,用于跟踪系统调用和信号

3. 测试示例

#include <stdio.h>
#include <stdlib.h>

void long_test() {
    int i, j;
    for (i = 0; i < 1000000; i++) j = i;
}

void foo2() {
    int i;
    for (i = 0; i < 10; i++) long_test();
}

void foo1() {
    int i;
    for (i = 0; i < 100; i++) long_test();
}
int main(void) {
    foo1();
    foo2();
}
  1. 编译成二进制文件

    g++ -o test test.cpp

  2. 使用 perf 对系统 CPU 事件做采样
    采样60s,会生成一个perf.data文件(采样时间可自行设定):

    #方式一:对一个正在运行的进程,进行采样
    perf record -p PID[这里换成要检测的进程ID] -g – sleep 60
    #方式二:全新运行一个二进制文件main,进行采样
    sudo perf record -F 99 -g ./test – sleep 60

  3. 安装火焰图
    利用这个开源工具可以将报告生成可视化的svg图片,更容易查看对应的CPU开销时间和调用栈深度:

    git clone --depth 1 https://github.com/brendangregg/FlameGraph.git
    #安装perl
    yum install -y perl

  4. 生成火焰图
    生成火焰图的脚本,对二进制文件main进行10秒的采样,然后生成火焰图。
    非root用户需要加sudo。

    perf record -g ./test sleep 10
    perf script -i perf.data &> perf.unfold
    #火焰图的两个功能
    ./FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
    ./FlameGraph/flamegraph.pl perf.folded > perf.svg

我自己的:

sudo perf record -g ./build_pc/dead_reckoning sleep 10
perf script -i perf.data &> perf.unfold
/home/yj/sda/third_party/FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
/home/yj/sda/third_party/FlameGraph/flamegraph.pl perf.folded > perf.svg
在这里插入图片描述

上面的方式中,[unknown] 出现过多,可考虑将 -g(默认为 fp) 修改为 --call-graph。可参考 使用 perf 进行性能分析时如何获取准确的调用栈

-优点缺点
fpNone默认 fp 被优化掉了根本不可用。
lbr高效准确需要较新的 Intel CPU 才有此功能;2. 能记录的调用栈深度有限。
dwarf准确1. 开销相对较大;2. 需要编译时附加了调试信息。

sudo perf record --call-graph dwarf ./build_pc/dead_reckoning sleep 10


//<-生成折叠后的调用栈,该命令从名为 perf.data 的 perf 数据文件中提取详细的性能事件样本信息,并以可读的文本格式输出
//<-这一步生成的 perf.unfold 文件包含了未折叠的调用栈信息,即每次采样的函数调用链
//<-perf.unfold 是上一步生成的包含详细调用栈信息的文件
sudo perf script -i perf.data &> perf.unfold


//<-生成火焰图
//<- ./stackcollapse-perf.pl 是 FlameGraph 工具集中的一部分,用于处理 perf 生成的未折叠调用栈数据。
//<-经过 stackcollapse-perf.pl 处理后的文件内容是对原始调用栈进行了折叠统计,
//<-即将同一调用路径下的事件次数累加,形成一种更紧凑的格式,便于生成火焰图。
/home/yj/sda/third_party/FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded


//<- 最后生成 svg 图
//<- ./flamegraph.pl 是 FlameGraph 工具集中用于生成火焰图的脚本
//<- 将火焰图生成的输出重定向到了名为 perf.svg 的SVG矢量图形文件中。
/home/yj/sda/third_party/FlameGraph/flamegraph.pl perf.folded > perf.svg
在这里插入图片描述

SVG是一种可缩放的矢量图形格式,生成的火焰图可以直接在浏览器中查看,形象地展示了各个函数在CPU执行过程中的调用关系及其占用时间比例,有助于定位性能瓶颈。

4. 从火焰图可以获得的信息

  • 调用栈从下往上,下层为父类,上层为子类。
  • 点击父类缩小,点击子类放大。
  • 只关注自己实现的函数名,忽略标准库中的函数
  • 总结一下,火焰图的宽度用于比较不同函数或代码路径的性能,而高度用于显示函数调用堆栈的深度

5. 生成火焰图常见问题

  1. Stack count is low (1). Did something go wrong?
    -> sudo perf script 时没加 root 权限。
  • 29
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
google-perftools 简介 google-perftools 是一款针对 C/C++ 程序的性能分析工具,它是一个遵守 BSD 协议的开源项目。使用该工具可以对 CPU 时间片、内存等系统资源的分配和使用进行分析,本文将重点介绍如何进行 CPU 时间片的剖析。 google-perftools 对一个程序的 CPU 性能剖析包括以下几个步骤。 1. 编译目标程序,加入对 google-perftools 库的依赖。 2. 运行目标程序,并用某种方式启动 / 终止剖析函数并产生剖析结果。 3. 运行剖结果转换工具,将不可读的结果数据转化成某种格式的文档(例如 pdf,txt,gv 等)。 安装 您可以在 google-perftools 的网站 (http://code.google.com/p/google-perftools/downloads/list) 上下载最新版的安装包。为完成步骤 3 的工作,您还需要一个将剖析结果转化为程序员可读文档的工具,例如 gv(http://www.gnu.org/software/gv/)。 编译与运行 您需要在原有的编译选项中加入对 libprofiler.so 的引用,这样在目标程序运行时会加载工具的动态库。例如本例中作者的系统中,libprofiler.so 安装在"/usr/lib"目录下,所以需要在 makefile 文件中的编译选项加入“-L/usr/lib -lprofiler”。 google-perftools 需要在目标代码的开始和结尾点分别调用剖析模块的启动和终止函数,这样在目标程序运行时就可以对这段时间内程序实际占用的 CPU 时间片进行统计和分析。工具的启动和终止可以采用以下两种方式。 a. 使用调试工具 gdb 在程序中手动运行性能工具的启动 / 终止函数。 gdb 是 Linux 上广泛使用的调试工具,它提供了强大的命令行功能,使我们可以在程序运行时插入断点并在断点处执行其他函数。具体的文档请参照 http://www.gnu.org/software/gdb/,本文中将只对用到的几个基本功能进行简单介绍。使用以下几个功能就可以满足我们性能调试的基本需求,具体使用请参见下文示例。 命令 功能 ctrl+c 暂停程序的运行 c 继续程序的运行 b 添加函数断点(参数可以是源代码中的行号或者一个函数名) p 打印某个量的值或者执行一个函数调用 b. 在目标代码中直接加入性能工具函数的调用,该方法就是在程序代码中直接加入调试函数的调用。 两种方式都需要对目标程序重新编译,加入对性能工具的库依赖。对于前者,他的好处是使用比较灵活,但工具的启动和终止依赖于程序员的手动操作,常常需要一些暂停函数(比如休眠 sleep)的支持才能达到控制程序的目的,因此精度可能受到影响。对于后者,它需要对目标代码的进行修改,需要处理函数声明等问题,但得到的结果精度较高,缺点是每次重新设置启动点都需要重新编译,灵活度不高,读者可以根据自己的实际需求采用有效的方式。 示例详解 该程序是一个简单的例子,文中有两处耗时的无用操作,并且二者间有一定的调用关系。 清单 1. 示例程序 void consumeSomeCPUTime1(int input){ int i = 0; input++; while(i++ < 10000){ i--; i++; i--; i++; } }; void consumeSomeCPUTime2(int input){ input++; consumeSomeCPUTime1(input); int i = 0; while(i++ < 10000){ i--; i++; i--; i++; } }; int stupidComputing(int a, int b){ int i = 0; while( i++ < 10000){ consumeSomeCPUTime1(i); } int j = 0; while(j++ < 5000){ consumeSomeCPUTime2(j); } return a+b; }; int smartComputing(int a, int b){ return a+b; }; void main(){ int i = 0; printf("reached the start point of performance bottle neck\n"); sleep(5); //ProfilerStart("CPUProfile"); while( i++ MyProfile.pdf 转换后产生的结果文档如下中的数字和框体的大小代表了的某个函数的运行时间占整个剖析时间的比例。由代码的逻辑可知,stupidComputing,stupidComputing2 都是费时操作并且它们和 consumeSomeCPUTime 存在着一定的调用关系。 1. 剖析结果 结束语 本文介绍了一个 Linux 平台上的性能剖析工具 google-perftools,并结合实例向读者展示了如何使用该工具配置、使用及分析性能瓶颈。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泠山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值