第2章:系统级性能分析工具:perf、valgrind、gperftools 的安装与使用,如何定位CPU热点与内存泄漏

做ROS2性能优化,最怕什么?

怕的是你埋头苦干改代码,结果改的根本不是瓶颈。我见过太多团队,花了两周优化一个函数,结果发现系统卡顿是因为内存泄漏导致频繁GC。所以,动手优化之前,先得学会「看」——看CPU到底在忙什么,看内存到底被谁吃了。

这一章,我就带你上手三把利器:perf、valgrind、gperftools。它们分别解决不同的问题:CPU热点、内存泄漏、性能剖析。嗯,咱们一个一个来。

核心逻辑:性能分析不是玄学,是科学。先定位,再优化。顺序永远是:
1. 用perf找CPU热点 → 2. 用valgrind查内存泄漏 → 3. 用gperftools做细粒度剖析。

系统级性能分析工具知识体系 性能分析三件套 perf CPU热点 / 采样分析 valgrind 内存泄漏 / 越界检测 gperftools 堆剖析 / 细粒度分析 • 采样模式 • 调用图生成 • memcheck • callgrind • CPU profiler • Heap profiler

2.1 perf:CPU热点定位利器

perf 是 Linux 内核自带的性能分析工具。说白了,它就是个「采样器」——每隔一段时间打断CPU,看看当前在跑什么代码。采样次数多了,热点函数自然就浮出水面了。

安装

Ubuntu 上安装很简单:

sudo apt-get install linux-tools-common linux-tools-generic
sudo apt-get install linux-tools-$(uname -r)

我个人习惯装完后先跑一下 perf list,看看硬件事件是否都支持。如果报错,多半是内核版本和工具版本不匹配,换个内核头文件包就行。

基本用法

假设你有一个ROS2节点在跑,想看看它CPU消耗在哪里:

# 采样30秒
sudo perf record -g -p $(pgrep -f your_node_name) -- sleep 30

# 生成报告
sudo perf report -g graph

-g 表示记录调用栈,这样你才能看到热点函数是被谁调用的。我在项目中遇到过,光看函数名根本看不出问题,加上调用链才发现是某个回调函数里调了一个O(n²)的算法。

小技巧:如果采样结果里看到大量 __lock_acquire 或 spin_lock,说明你的节点在锁竞争上消耗严重。这时候别急着优化算法,先看看锁的粒度是不是太大了。

火焰图生成

perf 自带的文本报告不够直观。我建议你配合火焰图脚本使用:

# 下载 FlameGraph 工具
git clone https://github.com/brendangregg/FlameGraph

# 生成火焰图数据
sudo perf script -i perf.data > out.perf

# 折叠调用栈
./FlameGraph/stackcollapse-perf.pl out.perf > out.folded

# 生成SVG火焰图
./FlameGraph/flamegraph.pl out.folded > flame.svg

打开SVG文件,宽条就是热点。你想想看,一眼就能看出哪个函数占用了80%的CPU时间,是不是比看数字爽多了?

2.2 valgrind:内存泄漏的照妖镜

ROS2节点跑着跑着内存暴涨,最后OOM被kill掉——这种问题我调过不下十次。valgrind 的 memcheck 工具就是专门干这个的。

安装

sudo apt-get install valgrind

嗯,这里要注意:valgrind 会让程序运行速度慢10-20倍,所以别在生产环境直接跑。我一般是在开发机上,用单元测试或者小数据量场景来检测。

基本用法

valgrind --tool=memcheck --leak-check=full \
         --show-leak-kinds=all \
         ./your_ros2_node

输出会告诉你:

  • definitely lost:明确泄漏的内存,必须修复
  • indirectly lost:指针丢失导致的内存泄漏
  • possibly lost:可能泄漏,需要人工确认
  • still reachable:程序退出时还没释放,但指针还在

避坑指南:我曾经在ROS2的callback group里分配了shared_ptr,但忘记在回调结束时reset。valgrind报了一堆「still reachable」,我一开始没在意,结果节点跑了48小时后内存涨了2GB。记住:still reachable 也可能是泄漏,尤其是长期运行的节点。

ROS2场景下的特殊配置

ROS2用了大量智能指针和DDS通信,直接跑valgrind会有很多误报。我建议加几个抑制规则:

# 创建 suppressions 文件
cat > ros2.supp << 'EOF'
{
   ignore_libstdcxx_allocations
   Memcheck:Leak
   ...
   obj:/usr/lib/x86_64-linux-gnu/libstdc++.so.6*
}
{
   ignore_fastdds_allocations
   Memcheck:Leak
   ...
   obj:/usr/lib/x86_64-linux-gnu/libfastrtps.so*
}
EOF

# 运行时加载抑制文件
valgrind --suppressions=ros2.supp --tool=memcheck ./your_node

2.3 gperftools:细粒度性能剖析

perf 是采样,valgrind 是模拟执行。那有没有一种工具,既能看CPU热点,又能看内存分配,而且开销还比较小?有,gperftools(原名Google Performance Tools)。

安装

sudo apt-get install google-perftools libgoogle-perftools-dev

CPU Profiler 使用

gperftools 的 CPU profiler 可以嵌入到你的程序里:

# 在代码中启用
#include <gperftools/profiler.h>

int main() {
    ProfilerStart("my_profile.prof");
    // ... 你的业务逻辑 ...
    ProfilerStop();
    return 0;
}

# 编译时链接
g++ -o my_node my_node.cpp -lprofiler

# 或者通过环境变量动态启用
CPUPROFILE=my_profile.prof ./your_ros2_node

生成的分析文件可以用 pprof 查看:

# 文本模式
pprof --text ./your_node my_profile.prof

# 图形模式(需要graphviz)
pprof --pdf ./your_node my_profile.prof > profile.pdf

个人经验:gperftools 的 CPU profiler 开销只有5%-10%,比valgrind轻量得多。我通常先用perf扫一遍,找到热点模块后,再用gperftools对这个模块做细粒度分析。这样效率最高。

Heap Profiler 使用

内存泄漏也可以用gperftools查,而且比valgrind快:

# 启用堆剖析
HEAPPROFILE=my_heap.prof ./your_ros2_node

# 查看堆快照差异
pprof --svg ./your_node my_heap.prof.0001.heap > heap1.svg
pprof --svg ./your_node my_heap.prof.0002.heap > heap2.svg

对比两个快照,你能清楚看到哪些函数分配的内存一直在增长。我在调一个SLAM节点时,就是用这个方法发现某个地图数据结构在每次更新时都new了新对象,但旧对象没释放。

2.4 实战:三件套组合拳

光讲工具不行,咱们来一个真实场景。假设你的ROS2节点CPU占用率居高不下,内存也在缓慢增长。怎么排查?

  1. 第一步:perf 扫全貌
    sudo perf record -g -p $(pgrep mapping_node) -- sleep 60
    sudo perf report -g graph
    发现热点在 PointCloud::filter() 函数,占CPU 45%。
  2. 第二步:valgrind 查内存
    valgrind --tool=memcheck --leak-check=full ./mapping_node
    报了一个 definitely lost: 1,024 bytes in 8 blocks,定位到 MapUpdater::update() 里有个vector没释放。
  3. 第三步:gperftools 细看
    CPUPROFILE=filter.prof HEAPPROFILE=filter_heap.prof ./mapping_node
    pprof --pdf ./mapping_node filter.prof > filter_hot.pdf
    发现 filter() 里有个循环每次都在重新分配临时数组,改成复用后CPU占用降到12%。

总结一下:perf 是望远镜,看全局;valgrind 是显微镜,查细节;gperftools 是手术刀,做精准切除。三件套配合使用,没有解决不了的性能问题。

好了,工具装好了,方法也讲了。下一章咱们就进入实战,用这些工具去优化一个真实的ROS2节点。到时候你会发现,原来性能瓶颈往往藏在你最意想不到的地方。