第26章 嵌入式性能分析:资源受限环境下的性能分析,使用ARM DSTREAM
嵌入式性能分析,说白了就是在资源受限的环境里,把每一丝性能都榨出来。我这些年调试过不少嵌入式系统,从智能手表到工业控制器,发现一个共性:没有合适的工具,性能优化就是盲人摸象。ARM DSTREAM 就是那个能让你「看见」系统运行细节的利器。
26.1 为什么需要 DSTREAM?
你可能会问:「我用 JTAG 调试器不也能看寄存器吗?」嗯,这里要注意:普通调试器只能做断点调试,看不到时间维度的性能数据。DSTREAM 不一样,它内置了硬件追踪能力,能实时捕获指令流、数据访问、中断响应等关键信息。
我在项目中遇到过这样一个场景:一个跑在 Cortex-M7 上的控制算法,偶尔会出现毫秒级的卡顿。用示波器看 IO 引脚翻转,只能知道「卡了」,但不知道卡在哪里。DSTREAM 的 ETM 追踪一开,立马发现是某个中断服务函数里有个循环等待,占用了太多 CPU 时间。
| 功能 | 普通 JTAG | DSTREAM |
|---|---|---|
| 断点调试 | ✅ | ✅ |
| 实时追踪 | ❌ | ✅ |
| 性能计数器 | ❌ | ✅ |
| 功耗分析 | ❌ | ✅ |
| 代码覆盖率 | ❌ | ✅ |
26.2 环境搭建与连接
DSTREAM 的连接其实不复杂,但有几个坑。我刚开始用的时候,就因为在目标板上没接好 TRACE 时钟,折腾了半天。
标准连接方式:
- JTAG/SWD 接口:用于调试控制,至少需要 TMS、TCK、TDI、TDO、nSRST
- TRACE 接口:用于数据追踪,需要 TRACECLK、TRACEDATA[0..3]
- 电源:DSTREAM 可以给目标板供电,但我建议单独供电,避免干扰
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")));
26.4 高级技巧:硬件性能计数器
DSTREAM 还能读取 CPU 内部的硬件性能计数器。这些计数器是芯片自带的,不占用额外资源。常用的有:
| 计数器 | 含义 | 典型值 |
|---|---|---|
| CYCLES | CPU 时钟周期数 | 程序总耗时 |
| INSTRUCTIONS | 执行的指令数 | 代码量 |
| L1D_CACHE_REFILL | L1 数据 cache 未命中次数 | 越低越好 |
| L1I_CACHE_REFILL | L1 指令 cache 未命中次数 | 越低越好 |
| BRANCH_MISPREDICT | 分支预测失败次数 | 影响流水线效率 |
我一般会先看 CPI(Cycles Per Instruction)。理想情况下,CPI 应该接近 1。如果 CPI 大于 2,说明有严重的流水线停顿。这时候再去看 cache 和分支预测的计数器,就能找到瓶颈。
26.5 知识体系图
下面这张图总结了本章的核心逻辑:
26.6 常见问题与避坑
最后分享几个我实际工作中遇到的坑:
- 追踪深度不够:DSTREAM 的缓冲区有限,如果程序运行时间太长,会覆盖掉前面的数据。我建议先跑一个短时间的测试用例,确认没问题后再跑长时间测试。
- 时钟同步问题:DSTREAM 和目标板的时钟必须同步。如果目标板用了 PLL 倍频,DSTREAM 可能无法正确解析追踪数据。解决办法是让 DSTREAM 使用目标板的 TRACECLK 作为参考时钟。
- 中断干扰:在采集性能数据时,尽量关闭不必要的中断。否则你会看到大量中断上下文切换的数据,干扰对主程序的分析。
好了,关于 DSTREAM 的性能分析就讲到这里。工具只是手段,关键还是要有分析思路。下次遇到性能问题,不妨先问自己三个问题:瓶颈在 CPU 还是内存?在中断还是主循环?在算法还是数据结构? 带着问题去用 DSTREAM,你会发现它真的能帮你「看见」答案。