性能优化
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录制优化
- 磁盘读写优化
(持续补充中)