第26章 嵌入式性能分析:资源受限环境下的性能分析,使用ARM DSTREAM

嵌入式性能分析,说白了就是在资源受限的环境里,把每一丝性能都榨出来。我这些年调试过不少嵌入式系统,从智能手表到工业控制器,发现一个共性:没有合适的工具,性能优化就是盲人摸象。ARM DSTREAM 就是那个能让你「看见」系统运行细节的利器。

26.1 为什么需要 DSTREAM?

你可能会问:「我用 JTAG 调试器不也能看寄存器吗?」嗯,这里要注意:普通调试器只能做断点调试,看不到时间维度的性能数据。DSTREAM 不一样,它内置了硬件追踪能力,能实时捕获指令流、数据访问、中断响应等关键信息。

我在项目中遇到过这样一个场景:一个跑在 Cortex-M7 上的控制算法,偶尔会出现毫秒级的卡顿。用示波器看 IO 引脚翻转,只能知道「卡了」,但不知道卡在哪里。DSTREAM 的 ETM 追踪一开,立马发现是某个中断服务函数里有个循环等待,占用了太多 CPU 时间。

核心能力对比:
功能普通 JTAGDSTREAM
断点调试
实时追踪
性能计数器
功耗分析
代码覆盖率

26.2 环境搭建与连接

DSTREAM 的连接其实不复杂,但有几个坑。我刚开始用的时候,就因为在目标板上没接好 TRACE 时钟,折腾了半天。

标准连接方式:

  • JTAG/SWD 接口:用于调试控制,至少需要 TMS、TCK、TDI、TDO、nSRST
  • TRACE 接口:用于数据追踪,需要 TRACECLK、TRACEDATA[0..3]
  • 电源:DSTREAM 可以给目标板供电,但我建议单独供电,避免干扰
我曾经踩过的坑:TRACE 时钟频率不能超过目标板 CPU 主频的 1/4。有一次我设了 100MHz 的追踪时钟,结果 CPU 才跑 200MHz,数据全乱了。后来查手册才发现这个限制。

26.3 性能分析实战流程

我个人习惯把性能分析分成三步:采集 → 分析 → 定位。下面用一个实际案例来演示。

26.3.1 采集阶段

先配置 DSTREAM 的追踪参数。在 DS-5 或 ARM Development Studio 里,打开 Trace Configuration 面板:

// 伪代码:配置 ETM 追踪
ETM_Config config;
config.trace_source = ETM_SOURCE_INSTRUCTION | ETM_SOURCE_DATA;
config.trace_depth = ETM_DEPTH_FULL;  // 全量追踪
config.filters = ETM_FILTER_NONE;     // 不过滤,先看全貌
config.clock_divider = 4;             // 追踪时钟分频

DSTREAM_Configure(&config);
DSTREAM_StartTrace();

这里有个技巧:第一次跑的时候不要加任何过滤条件。先看全貌,再逐步缩小范围。我见过有人一上来就设一堆过滤条件,结果漏掉了关键信息。

26.3.2 分析阶段

采集完成后,用 Streamline 工具打开追踪数据。你会看到类似这样的界面:

关键指标解读:
  • CPU 利用率曲线:看整体负载,有没有长时间 100% 的情况
  • 中断延迟直方图:看中断响应时间分布,有没有异常长尾
  • 函数调用栈:看热点函数,哪个函数占用了最多 CPU 时间
  • Cache 命中率:看数据局部性好不好,有没有频繁的 cache miss

举个例子,我曾经分析过一个音频处理程序。从 CPU 利用率曲线看,每 10ms 有一个尖峰,持续 2ms。直觉告诉我这是音频中断处理。但点开函数调用栈一看,发现中断处理里调了一个 memcpy,占用了 60% 的时间。

26.3.3 定位阶段

定位到 memcpy 后,我进一步查看数据追踪,发现它拷贝的是一个 4KB 的音频缓冲区。但问题是,这个缓冲区在外部 SDRAM 里,而 CPU 的 cache 只有 16KB。每次 memcpy 都会导致大量 cache miss。

优化方案其实很简单:把音频缓冲区放到内部 SRAM 里。改完链接脚本后,中断处理时间从 2ms 降到了 0.3ms。

// 优化前:缓冲区在外部 SDRAM
uint8_t audio_buffer[4096] __attribute__((section(".sdram")));

// 优化后:缓冲区在内部 SRAM
uint8_t audio_buffer[4096] __attribute__((section(".sram")));
小提示:内部 SRAM 通常很小,但速度极快。把高频访问的数据放进去,往往能带来 5-10 倍的性能提升。

26.4 高级技巧:硬件性能计数器

DSTREAM 还能读取 CPU 内部的硬件性能计数器。这些计数器是芯片自带的,不占用额外资源。常用的有:

计数器含义典型值
CYCLESCPU 时钟周期数程序总耗时
INSTRUCTIONS执行的指令数代码量
L1D_CACHE_REFILLL1 数据 cache 未命中次数越低越好
L1I_CACHE_REFILLL1 指令 cache 未命中次数越低越好
BRANCH_MISPREDICT分支预测失败次数影响流水线效率

我一般会先看 CPI(Cycles Per Instruction)。理想情况下,CPI 应该接近 1。如果 CPI 大于 2,说明有严重的流水线停顿。这时候再去看 cache 和分支预测的计数器,就能找到瓶颈。

26.5 知识体系图

下面这张图总结了本章的核心逻辑:

DSTREAM 性能分析知识体系 硬件连接与配置 ETM 追踪数据采集 Streamline 可视化 性能计数器分析 函数调用栈分析 热点函数定位 Cache 瓶颈分析 中断延迟分析 代码优化与验证

26.6 常见问题与避坑

最后分享几个我实际工作中遇到的坑:

  • 追踪深度不够:DSTREAM 的缓冲区有限,如果程序运行时间太长,会覆盖掉前面的数据。我建议先跑一个短时间的测试用例,确认没问题后再跑长时间测试。
  • 时钟同步问题:DSTREAM 和目标板的时钟必须同步。如果目标板用了 PLL 倍频,DSTREAM 可能无法正确解析追踪数据。解决办法是让 DSTREAM 使用目标板的 TRACECLK 作为参考时钟。
  • 中断干扰:在采集性能数据时,尽量关闭不必要的中断。否则你会看到大量中断上下文切换的数据,干扰对主程序的分析。
特别提醒:DSTREAM 的探针线缆比较脆弱,不要频繁插拔。我有一根线就是因为经常弯折,内部断了,导致追踪数据时好时坏。后来换了新线才解决。

好了,关于 DSTREAM 的性能分析就讲到这里。工具只是手段,关键还是要有分析思路。下次遇到性能问题,不妨先问自己三个问题:瓶颈在 CPU 还是内存?在中断还是主循环?在算法还是数据结构? 带着问题去用 DSTREAM,你会发现它真的能帮你「看见」答案。

公众号:蓝海资料掘金营,微信 deep3321