一、基本使用#
# 以 99Hz 频率采样,记录 CPU 正在执行的位置
perf record -F 99 -g ./my_program
# -F 99:每秒采样 99 次(避免采样开销过大)
# -g:记录调用栈(才能看到谁调了谁)
# 查看热点函数排名
perf report --stdio
# 输出示例:
# Samples: 1K of event 'cycles', 4000 Hz
# Event count (approx.): 845117845
# Overhead Command Shared Object Symbol
# 25.32% program program [.] compute_dct
# 18.15% program program [.] motion_search
# 12.07% program program [.] avcodec_send_frame
# 8.50% program libc-2.31.so [.] memcpy
# 5.23% program program [.] frame_copy
# ...
# 看到热点函数后,用 perf annotate 查看汇编级别的热点
perf annotate compute_dct
二、perf annotate 的汇编级分析#
输出示例:
compute_dct:
│ 循环展开的热点:
│ dct_4x4.c:25 (循环主体)
│ vld1.32 {q0}, [r1]! ← 加载数据
23.5% │ vmla.f32 q1, q0, q2 ← 乘加运算(占 23.5% 的 CPU 时间)
12.1% │ vld1.32 {q3}, [r2]! ← 再加载数据
18.3% │ vmla.f32 q4, q3, q5 ← 乘加运算
│ subs r3, #1
5.2% │ bne loop ← 分支
如果一条指令占的时间异常高(比如 50%+),说明这条指令可能是流水线停滞点。
常见原因:
- cache miss(加载指令)
- 除法(div 指令,几十个周期)
- 浮点转换(fcvt)
三、C++ 实战:用 perf 找到热点函数#
// bench.cpp —— 一个需要性能分析的程序
#include <vector>
#include <algorithm>
#include <cmath>
// 模拟视频编码中的运动估计
void motion_search(const std::vector<int>& frame, int& best_mv) {
best_mv = 0;
int best_cost = INT_MAX;
for (int dy = -16; dy <= 16; dy++) {
for (int dx = -16; dx <= 16; dx++) {
int cost = 0;
// 简化的 SAD(绝对差和)计算
for (int i = 0; i < 64; i++) {
int diff = frame[i] - frame[i + dy * 64 + dx];
cost += std::abs(diff);
}
if (cost < best_cost) {
best_cost = cost;
best_mv = dy * 64 + dx;
}
}
}
}
// 模拟 DCT 变换
void dct_transform(std::vector<float>& block) {
for (int i = 0; i < 64; i++) {
block[i] = std::sin(block[i]) * std::cos(block[i]); // 假运算模拟负载
}
}
int main() {
std::vector<int> frame(4096, 128);
std::vector<float> dct_block(64, 1.0f);
for (int i = 0; i < 100000; i++) {
if (i % 10 == 0) {
dct_transform(dct_block); // 每 10 次做一次 DCT
} else {
int mv;
motion_search(frame, mv); // 大多数时间做运动估计
}
}
}
# 编译并运行 perf
g++ -O2 -g -o bench bench.cpp
perf record -F 99 -g ./bench
perf report --stdio
# 预期输出:
# Overhead Symbol
# 72.5% motion_search ← 主要热点,占总 CPU 的 72%
# 18.3% dct_transform ← 次要热点
# 5.2% std::abs ← 被 motion_search 调用的
# 优化方向:把 motion_search 的 -16..16 搜索区域减少、提前终止等