【工具】使用perf抓取火焰图

背景

当程序存在cpu性能问题时,我们需要找到是哪个函数占用较多的CPU,也就是找出热点函数;perf的火焰图就是这个用途

安装

在Linux系统中,perf 是 Linux 内核提供的性能分析工具,它通常包含在内核源代码包中。大多数现代的Linux发行版都提供了预编译的 perf 包,可以直接通过系统的包管理器安装。

根据你所使用的Linux发行版,以下是一些不同发行版上安装 perf 的指令:

对于基于Debian的系统(比如Ubuntu):

bash 复制代码
sudo apt-get update
sudo apt-get install linux-tools-common linux-tools-generic linux-tools-`uname -r`

请注意,linux-tools-generic 包可能会安装不与你当前运行的内核版本完全匹配的 perf 版本。为了确保版本对应,使用 uname -r 来获取并安装与当前运行内核版本对应的 perf 版本。

对于基于Red Hat的系统(比如Fedora和CentOS):

对于 Fedora 你可以使用:

bash 复制代码
sudo dnf install perf

对于CentOS或者RHEL,可以使用:

bash 复制代码
sudo yum install perf

对于Arch Linux:

bash 复制代码
sudo pacman -S linux-tools

在安装后,你可以通过键入 perf --version 来检查 perf 是否成功安装以及其版本信息。

如果你需要更高级的功能,或者你的系统没有预编译的 perf 可用,你也可以选择从Linux内核源代码编译 perf 工具。这个过程较为复杂,需要下载相应版本的Linux内核源代码,并按照源代码中的说明来编译 perf。

样例代码

设计如下的程序:有两个线程,分别运行func1->func1_1(初始化大小为10的vector,0-9)、func1_2(初始化大小为100的vector,0-99); func2->func2_1(初始化大小为1000的vector,0-999)、func2_2(初始化大小为10000的vector,0-9999)

cpp 复制代码
#include <iostream>
#include <thread>
#include <vector>

void func1_1() {
    std::vector<int> v(10);
    int i = 0;
    for(auto e : v) {
        e = i++;
    }
}

void func1_2() {
    std::vector<int> v(100);
    int i = 0;
    for(auto e : v) {
        e = i++;
    }
}

void func2_1() {
    std::vector<int> v(1000);
    int i = 0;
    for(auto e : v) {
        e = i++;
    }
}

void func2_2() {
    std::vector<int> v(10000);
    int i = 0;
    for(auto e : v) {
        e = i++;
    }
}

void func1() {
    while(true) {
        func1_1();
        func1_2();
    }
}

void func2() {
    while(true) {
        func2_1();
        func2_2();
    }
}

int main() {
    std::thread t1(&func1);
    std::thread t2(&func2);
    if(t1.joinable()) {
        t1.join();
    }

    if(t2.joinable()) {
        t2.join();
    } 
    return 0;
}

编译

bash 复制代码
g++ perf_demo.cpp -o perf_demo -lpthread

采集perf数据

  1. 找到要采集程序的pid

  2. 使用perf record命令来收集程序运行时的性能数据

    bash 复制代码
    perf record -F 99 -p 12519 -g -- sleep 30

    参数解释如下:

    • -F 99:指定采样频率(99 次/秒),可以根据需要调整该值。
    • -p:要采集的进程pid。
    • -g:记录调用栈信息,这对于生成火焰图是必需的。
    • -- sleep 30:收集性能数据的持续时间,这里设置为30秒。sleep 30 表示在收集数据后暂停30秒,以确保 perf 有足够的时间来记录信息。
  3. 然后会在当前目录创建一个名 perf.data的文件,其中包含了采样数据

  4. 生成火焰图

    • 下载安装FlameGraph工具,它是一组脚本,用来从 perf 的采样数据生成可视化的火焰图。

      安装方式:

      shell 复制代码
      git clone https://github.com/brendangregg/FlameGraph.git
    • 按照前面提到的步骤,首先安装并使用 FlameGraph 工具转换采样数据

      shell 复制代码
      perf script | /path/to/FlameGraph/stackcollapse-perf.pl > out.perf-folded
    • 使用转换后的数据生成SVG火焰图

      shell 复制代码
      /path/to/FlameGraph/flamegraph.pl out.perf-folded > perf_demo.svg

火焰图分析

火焰图提供了程序执行过程中的调用栈样本的可视化表示,它以一种直观的方式展示了哪些函数消耗了最多的CPU时间。下面是如何分析火焰图的一些基本步骤和技巧:

  1. 理解火焰图布局
  • 水平方向:显示了采样到的栈帧。每个横向的条块代表一个函数调用,其宽度代表该函数在采样中占据的相对时间比例。宽度越大,说明函数占用的CPU时间越长。
  • 垂直方向:展示了调用栈的深度。栈底部(图的最底层)通常是主函数或线程的起始点,更上层则代表函数的调用关系(被调用者在调用者之上)。
  1. 分析热点(Hot Spots)
    找到最宽的几个柱形区域,这些通常是性能的热点,即你的程序花费时间最多的地方。有时候,一个宽柱形可能是因为单个函数调用自身就很耗时,有时则是由于该函数被频繁调用。
  2. 检查调用路径
    从任意一个宽柱形开始,向上追踪它的调用路径。这可以帮助你理解为什么一个函数会变成热点。可能是它自身操作复杂,或者它被一个循环结构频繁调用。
  3. 注意递归调用
    如果你看到在火焰图中有重复的模式,这可能表示递归调用。递归调用可能是性能问题的来源,特别是当递归没有正确优化时。
  4. 考虑不同层级的影响
    尽量避免只关注顶部的函数。如果一个底层函数(位于火焰图较低位置)相对较宽,它可能被多个不同的上层函数调用,改进这样一个底层函数可能对整个程序性能有显著的提升。
  5. 比较前后变化
    如果你在进行性能优化工作,应该在更改代码后重新生成火焰图,然后将新旧火焰图进行比较。这将帮助你直观地看到更改对性能的影响。
  6. 关注宽度变化
    如果你注意到某个函数的子调用中有明显的宽度变化,这可能表示性能问题。它可能是计算密集型操作,或者存在条件判断导致的执行路径变化。

根据上面样例抓取的火焰图进行分析:

  1. func1(50%) 和 func2(50%) 宽度各占一半 -- 两个线程,分别占使用cpu的一半
  2. func1_1(7%)、func1_2(36%)、func2_1(4%)、func2_2(39%)
  3. 可以得出来,热点函数是func1_2、func2_2

perf的原理

perf 是 Linux 内核提供的性能分析工具,可以用来进行系统级和进程级的性能分析。它使用了Linux内核中的性能计数器子系统 perf_events。以下是 perf 工具的基本工作原理:

  1. 性能计数器(Hardware Performance Counters)

    现代的CPU通常都带有硬件性能计数器,这些计数器可以在不影响系统性能的情况下,跟踪处理器内部事件,如指令执行次数、缓存命中/未命中次数等。

  2. perf_events 内核子系统

    perf 基于Linux内核中的 perf_events 子系统,后者提供了一种统一的接口来访问CPU硬件计数器以及其他监控事件,比如软件事件(上下文切换、页面缺失)和追踪点(tracepoints)。perf_events 子系统支持各种架构,并且是可扩展的。

  3. 事件采样(Sampling)

    通过 perf record 命令时,perf 会配置 perf_events 开始收集数据。通常是基于事件采样的方式进行的,即按照一定频率记录特定事件发生的时候的系统状态。当硬件计数器达到预设的阈值(采样频率),就会生成一个中断,perf 便在这个时候捕获当前的程序计数器(PC)、调用栈和其他寄存器的值等信息。

  4. 记录和分析

    采样得到的数据被写入到一个文件中(默认为 perf.data)。perf record 命令完成后,可以使用 perf report 查看和分析性能数据,或者使用 perf script 将数据导出供其他工具(如 FlameGraph)进一步分析。

  5. 调用栈捕获

    当启用 -g 选项时,perf 不仅会记录下触发采样的那一刻的程序计数器位置,还会尝试捕获当前的调用栈。这对于理解程序行为至关重要,尤其是识别哪些函数调用导致了大量的CPU使用。

  6. CPU和内核支持

    由于 perf 直接与CPU硬件和内核模块交互,它需要CPU支持特定的性能计数器特性,同时也需要内核支持 perf_events 接口。因此,旧的CPU或者特定的内核编译配置可能无法使用 perf 的某些功能。

综合以上,perf 的原理是通过硬件性能计数器和 perf_events 内核子系统,提供了一种方法来测量和分析系统运行时的详细性能指标,从而使开发者能够深入理解系统的瓶颈和性能问题所在。

一些思考

可以参考perf的设计,对自己的模块设计性能采集和分析工具,profiling工具。

相关推荐
阿部多瑞 ABU25 分钟前
`chenmo` —— 可编程元叙事引擎 V2.3+
linux·人工智能·python·ai写作
徐同保1 小时前
nginx转发,指向一个可以正常访问的网站
linux·服务器·nginx
HIT_Weston1 小时前
95、【Ubuntu】【Hugo】搭建私人博客:_default&partials
linux·运维·ubuntu
实心儿儿1 小时前
Linux —— 基础开发工具5
linux·运维·算法
oMcLin2 小时前
如何在SUSE Linux Enterprise Server 15 SP4上通过配置并优化ZFS存储池,提升文件存储与数据备份的效率?
java·linux·运维
王阿巴和王咕噜6 小时前
【WSL】安装并配置适用于Linux的Windows子系统(WSL)
linux·运维·windows
布史6 小时前
Tailscale虚拟私有网络指南
linux·网络
水天需0106 小时前
shift 命令详解
linux
wdfk_prog6 小时前
[Linux]学习笔记系列 -- 内核支持与数据
linux·笔记·学习
Xの哲學7 小时前
深入剖析Linux文件系统数据结构实现机制
linux·运维·网络·数据结构·算法