• 正文
  • 相关推荐
申请入驻 产业图谱

基于perf-cpp的嵌入式 Linux 代码段性能测量

7小时前
138
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

板子上 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 —— EventCounterSampler / 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-instructioncache-miss-ratio

Live events(不停止计数器也能读,底层在 x86 上可走 rdpmc,适合紧循环分段测)像 perf recordperf mem 一样做采样,可导出 CSV、火焰图相关格式,甚至写出 perf.data混用通用事件名与厂商专用 PMU 事件同类工具还有 PAPI、Likwid、更轻的 PerfEvent 等;perf-cpp 的定位是「C++ 内嵌、面向代码段、围绕 Linux perf」。

典型流程:埋点、运行、采集与解读

别把流程想复杂。工程上就是这四步,改完再跑一轮对比数字。

埋点:选 EventCounter 或 Sampleraddtrigger 声明关心的事件

运行start() → 可疑代码 → stop()(尽量别把无关日志、sleep 包进去)

收数result(),或 to_csvto_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/

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

本公众号专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!