Skip to main content

性能优化

CPU Loading优化

目标

  • 节点cpu占用均值达到xx%;
  • 节点cpu占用峰值达到xx%;

方法

  • 定位热点函数;
    • 使用perf等工具绘制火焰图定位cpuloading占用高的热点函数;
    • 均值优化:观察全任务流程中热点函数进行优化;
    • 峰值优化:观察峰值出现的特定时间段内热点函数;
  • 针对性优化

perf使用

安装

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

检查是否安装成功

perf --version

数据采集

编译

编译debug包(以ROS项目为例):

catkin_make -j12 -DCMAKE_BUILD_TYPE=Debug

对于一般的C/C++项目:

# 使用CMake
cmake -DCMAKE_BUILD_TYPE=Debug ..
make

# 或直接使用g++/gcc添加调试信息
g++ -g -O0 main.cpp -o main

权限设置

解除内核符号访问限制:

echo 0 | sudo tee /proc/sys/kernel/kptr_restrict

设置性能监控权限:

# 查看当前值
cat /proc/sys/kernel/perf_event_paranoid

# 设置权限(根据需要选择)
sudo sysctl -w kernel.perf_event_paranoid=1 # 采样当前用户启动的进程,不能监控其他用户或root的进程
sudo sysctl -w kernel.perf_event_paranoid=-1 # 采样所有进程

perf_event_paranoid 参数说明:

  • -1: 允许采样所有进程和内核(最宽松)
  • 0/1: 允许采样当前用户的进程和内核
  • 2: 仅允许采样当前用户的进程(默认值)
  • 3: 禁止采样(最严格)

注意: 降低权限值会降低系统安全性,建议仅在开发环境使用。

开始监控

首先启动目标程序,再输入以下命令开始记录perf监控数据:

perf record -p $(pidof test_node) -e cpu-clock -g -F 99 -o perf.data &

命令说明:

  • -p $(pidof test_node):指定要监控的进程ID
  • -e cpu-clock:监控CPU时钟事件
  • -g:记录调用栈信息(用于生成火焰图)
  • -o perf.data:指定输出文件名
  • -F 99:采样频率 99Hz(降低开销,实际可按需要调整,perf默认约为1000Hz)
  • &:后台运行

命令会在后台运行。

可以实时使用ls -lh perf.data命令看到这个.data文件在不断变大。

结束监控

输入以下命令查看perf进程:

ps aux | grep perf

杀掉进程:

kill <pid>

或者使用 killall 命令:

sudo killall perf

监控结束后,检查perf.data文件是否成功生成:

ls -lh perf.data

结果分析

准备Flame Graph工具

在工作目录下,拉取Flame Graph:

git clone https://github.com/brendangregg/FlameGraph.git

将perf.data拷贝到FlameGraph/目录下。

解析perf数据,绘制火焰图
# 用perf script工具对perf.data进行解析
perf script -i perf.data > perf.unfold

# 将perf.unfold中的符号进行折叠
./stackcollapse-perf.pl perf.unfold > perf.folded

# 生成火焰图
./flamegraph.pl perf.folded > perf.svg
火焰图解释

坐标轴含义:

  • X轴(宽度):每个调用栈路径按字母顺序排列,宽度表示该调用路径在采样中出现的频率(越宽越耗时)
  • Y轴(高度):调用栈深度,从下往上表示调用链
  • 颜色:通常随机分配,用于区分不同函数,不代表性能好坏

关键理解:

  • 宽度代表被采样到的总执行时间(CPU占用时间),不是该函数的调用次数
  • 例如:函数A调用10次每次执行1秒(总10秒),函数B调用1次每次执行5秒(总5秒),火焰图中函数A的宽度会是函数B的2倍
  • 顶部平坦且较宽的函数通常是性能热点
  • 火焰图的宽度 = 该调用栈路径被采样到的次数
  • 采样次数多 → 总执行时间长 → 火焰图宽度大
火焰图分析

重要理解:火焰图主要显示总执行时间,无法直接区分"调用次数多但每次执行时间短"和"调用次数少但每次执行时间长"。使用如下间接方法分析:

方法1:使用perf report查看采样分布

# 生成报告并查看
perf report -i perf.data --stdio

在报告中可以查看:

  • 采样次数(Samples):代表该函数被采样到的次数(反映执行时间,不是调用次数)
  • 百分比(%):占用的时间百分比
  • 结合调用栈和函数内部分析,可以判断函数的性能特征

方法2:使用perf annotate分析函数内部

# 查看特定函数的详细分析
perf annotate -i perf.data 函数名

这会显示函数内部哪些指令最耗时,帮助理解单次执行的性能瓶颈。如果函数内部某几条指令占用大部分采样,说明单次执行时间长。

方法3:使用perf script分析调用模式

# 查看函数在调用栈中出现的模式
perf script -i perf.data | grep 函数名 | wc -l

通过观察该函数在采样记录中的出现频率和分布,可以推断其调用模式。

方法4:结合调用次数统计工具

要准确统计调用次数,需要使用专门工具:

  • 代码插桩:在函数入口/出口添加计数器
  • gdb:设置断点并统计命中次数
  • strace:跟踪系统调用
  • valgrind --tool=callgrind:详细的调用图分析

性能热点优化

主要优化手段

  • 编译器优化与硬件加速
  • 数学计算优化
    • fast-math
    • 三角函数、平方根等
    • Eigen
  • 关键算法优化思路
    • 局部增量更新
    • 延迟计算
    • 空间换时间,缓存关键中间结果
    • 热启动
    • 数据结构优化
    • 提前返回
    • 区分大数据量与小数据量,分别使用不同的算法处理。比如排序算法
    • 依据当前cpu loading状态,择机选择近似算法或者精确算法
  • 关键算子优化流程
    • 算子的原理分析,评估算子的计算量
    • 评估优化预期收益
    • benchmark效果对比验证
  • 代码实现优化
    • 缓存友好
      • 时间局部性
      • 内存连续性访问
    • 分支预测友好
    • 零拷贝
  • 系统框架优化
    • 线程优化:优化线程模型和调度策略
    • 无锁设计
    • 线程池
    • 内存池
    • 日志优化
    • Topic录制优化
    • 磁盘读写优化

(持续补充中)