【工具】使用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工具。

相关推荐
binqian1 小时前
【Linux】mnt命名空间-操作
linux·运维·服务器
❀͜͡傀儡师1 小时前
CentOS 7 下升级 OpenSSL
linux·运维·centos
.信.2 小时前
书生实战营第四期-第三关 Git+InternStudio
linux·人工智能·git·python
rr最叨2 小时前
Linux基础知识作业
linux·运维·服务器
Karoku0662 小时前
【缓存与加速技术实践】Redis 主从复制
linux·运维·服务器·数据库·redis·缓存
hugerat2 小时前
嵌入式linux跨平台基于mongoose的TCP C++类的源码
linux·c++·tcp/ip
重生之我是数学王子2 小时前
网络编程 TCP编程 Linux环境 C语言实现
linux·服务器·c语言·网络·tcp/ip
1037号森林里一段干木头2 小时前
ubuntu双屏只显示一个屏幕另一个黑屏
linux·运维·ubuntu
Хайде2 小时前
VIM的使用
linux·编辑器·vim
Dragon_qu·x3 小时前
Mac 配置SourceTree集成云效
linux·git·云计算·mac