板子上 CPU 占用莫名其妙冲到 90%,某条消息路径偶发多几十毫秒,或者算法改完感觉「应该更快」——嵌入式 Linux 上这些事很常见。
top 只能告诉你谁在吃 CPU,gprof 又往往偏粗、偏侵入。
大家最终都会摸到 Linux 的 perf:能看 cycles、cache miss、采样热点,能力是真的强。
麻烦在于:命令行 perf 更擅长「整进程/整系统」地看一眼。你真正想比的,经常只是某一个排序、某一次哈希查找、某一段 DMA 完成后的用户态处理。启动、配置解析、日志刷屏全混进火焰图里,结论就被噪声冲淡了。
perf-cpp干的就是这件事:在 C++ 里对指定代码段做硬件性能计数和采样,底层仍走 Linux 的 perf_event_open,但 API 是 start() / stop() 这种埋点方式。
https://github.com/jmuehlig/perf-cpp
为何需要代码段级性能测量
传统工具默认视角偏「全局」。一次 perf record 可能把程序从启动到收尾全录进去;而你加班排查的,可能只是 process_frame() 里那 2ms。
perf-cpp 的卖点很直白:把计数器包在你指定的区间周围。官方 README 也反复强调这一点——可以只 profile 一个排序,或只数一次哈希查找的 cache miss;两个分配器各自包一层,才能算公平对比。
event_counter.start();
quick_sort(data); // 只测这段
event_counter.stop();
event_counter.start();
std::sort(data); // 再测这段
event_counter.stop();
这种「定点测速」对嵌入式特别有用:资源紧、路径长、外部干扰多,越全局越容易被初始化、喂狗、网络线程带偏。
Linux perf 与硬件 PMU 简介
现代 CPU 里有一组硬件寄存器,叫 PMU(Performance Monitoring Unit)。它们能在几乎不打扰软件的情况下累加底层事件:退休指令数、时钟周期、cache miss、分支预测失败、TLB miss 等。每核可编程计数器通常只有几个(常见 4~8 个量级);事件开太多,内核会做时间片轮转(multiplex),数字就从「精确」变成「按时间比例估算」。
用法大体分两种:
| 方式 | 直觉 | 你得到什么 |
|---|---|---|
| 计数(Counting) | 像 perf stat |
这段代码一共多少 cycles / miss |
| 采样(Sampling) | 像 perf record |
每隔 N 次事件抓一次现场(IP、时间、CPU…) |
计数回答「发生了多少次」;采样回答「主要卡在哪」。嵌入式排查时,我一般先计数把 CPI、miss ratio 摸清楚,数字离谱再采样。
perf-cpp 的软件栈定位
可以把栈想成四层:
你的业务代码 —— 真正要优化的路径
perf-cpp —— EventCounter / Sampler / Metrics,负责配置事件、启停、读结果、导出 CSV/JSON
Linux perf 子系统 —— perf_event_open、权限、事件调度、采样 ring buffer
硬件 PMU —— 真正累加的那几个计数器
也就是说:它不是「另一个 profiler GUI」,而是 把 perf 能力嵌进你的 C++ 程序。命令行 perf 仍然有用(系统级扫描、perf report);perf-cpp 更适合「库/算法级 A/B」和「只关心某段热路径」。
仓库相关能力(与官方文档一致)大致包括:
像 perf stat 一样记录硬件事件,但只包住你指定的代码内置/自定义
Metrics(如 cycles-per-instruction、cache-miss-ratio)
Live events(不停止计数器也能读,底层在 x86 上可走 rdpmc,适合紧循环分段测)像 perf record / perf mem 一样做采样,可导出 CSV、火焰图相关格式,甚至写出 perf.data混用通用事件名与厂商专用 PMU 事件同类工具还有 PAPI、Likwid、更轻的 PerfEvent 等;perf-cpp 的定位是「C++ 内嵌、面向代码段、围绕 Linux perf」。
典型流程:埋点、运行、采集与解读
别把流程想复杂。工程上就是这四步,改完再跑一轮对比数字。
埋点:选 EventCounter 或 Sampler,add / trigger 声明关心的事件
运行:start() → 可疑代码 → stop()(尽量别把无关日志、sleep 包进去)
收数:result(),或 to_csv / to_json,采样还可做符号解析
解读:看 CPI、miss ratio、分支失败率;两个实现横向比;再决定改算法还是改数据布局
事件计数:基本用法
依赖:GCC ≥ 11 或 Clang ≥ 14,C++17,CMake ≥ 3.10,Linux Kernel ≥ 4.0。构建示例:
git clone https://github.com/jmuehlig/perf-cpp.git
cd perf-cpp
cmake . -B build
cmake --build build
CMake 工程可用 FetchContent 拉进来(文档推荐),然后 target_link_libraries(your_target perf-cpp)。
最小计数示意(与官方 README / 文档一致):
#include <perfcpp/event_counter.hpp>
#include <iostream>
int main() {
auto event_counter = perf::EventCounter{};
// 普通事件 + 内置 metric 都可以 add
event_counter.add({
"seconds",
"instructions",
"cycles",
"cache-misses",
"cycles-per-instruction"
});
// 可选:提前 open,把配置开销踢出测量窗口
event_counter.open();
event_counter.start();
critical_path(); // 只包住你要测的代码
event_counter.stop();
const auto result = event_counter.result();
std::cout << result.to_string() << std::endl;
// 也可:result.to_csv("out.csv"); result.to_json("out.json");
event_counter.close();
return 0;
}
仓库示例 examples/statistics/single_thread.cpp 还会用 result(benchmark.size()) 做「按 cache line / 按迭代」归一化,做 A/B 时很实用。
事件采样:从统计次数到定位热点
计数告诉你 miss 很多;采样帮你对准哪条指令、哪次访存。官方 Quick Start:
#include <perfcpp/sampler.hpp>
#include <iostream>
int main() {
auto sampler = perf::Sampler{};
// 每 50000 个 cycles 采一次
sampler.trigger("cycles", perf::Period{50000U});
sampler.values()
.timestamp(true)
.cpu_id(true)
.logical_instruction_pointer(true);
sampler.start();
critical_path();
sampler.stop();
const auto samples = sampler.result();
samples.to_csv("samples.csv");
for (const auto& record : samples) {
const auto ts = record.metadata().timestamp().value();
const auto cpu = record.metadata().cpu_id().value();
const auto ip =
record.instruction_execution().logical_instruction_pointer().value();
std::cout << "Time=" << ts << " CPU=" << cpu
<< " IP=0x" << std::hex << ip << std::dec << "n";
}
// 注意:读完 samples 再 close
sampler.close();
return 0;
}
示例 examples/sampling/instruction_pointer.cpp 还会接 perf::util::SymbolResolver 把 IP 解析成符号;另有火焰图导出、perf.data 写出、内存地址采样等示例。
总结
性能问题最怕的是「凭感觉改、凭感觉赢」。perf-cpp 把 Linux perf 收成 C++ 埋点:你选定一段代码,拿到 cycles、miss、CPI,必要时再采样对准 IP。
仓库与文档:
- 源码:https://github.com/jmuehlig/perf-cpp文档:https://jmuehlig.github.io/perf-cpp/
138