性能分析工具:TouchGFX Designer 的 Performance Analyzer 与硬件定时器打点法

做界面优化,最怕什么?

怕的是凭感觉调。改个图片格式,觉得快了;换个字体,又觉得卡了。到底快了多少?卡在了哪里?全凭肉眼判断,这不行。

这一章,我们就来解决「怎么测」的问题。我会介绍两套工具:一套是 TouchGFX Designer 自带的 Performance Analyzer,另一套是我个人很依赖的硬件定时器打点法。前者适合快速看整体,后者适合深挖细节。

2.1 Performance Analyzer:开箱即用的帧率监视器

TouchGFX Designer 里藏着一个很实用的性能分析面板。你不需要额外接线,不需要改代码,点几下鼠标就能看到当前界面的刷新表现。

2.1.1 怎么打开它?

在 Designer 的右下角,找到「Performance Analyzer」标签。点击后,它会以浮动窗口的形式出现。我个人习惯把它固定在右侧,这样调试时一目了然。

小提示: 如果你在模拟器里运行,Performance Analyzer 默认是开启的。但如果你在真机上跑,需要确保工程配置里启用了 USE_SIMULATOR 相关的宏定义。否则,这个窗口不会显示任何数据。

2.1.2 核心指标怎么看?

窗口里主要显示三个数据:

  • FPS(帧率):当前界面每秒刷新的帧数。60 FPS 是理想值,低于 30 就会明显感觉卡顿。
  • CPU Load(CPU 负载):渲染一帧占用了多少 CPU 时间。这个值越高,留给其他任务的时间就越少。
  • Draw Calls(绘制调用次数):每帧向 GPU 发送了多少次绘制命令。这个数字直接反映了界面的复杂度。

我举个例子。有一次,我做一个仪表盘界面,FPS 只有 25。打开 Performance Analyzer 一看,Draw Calls 高达 180 多次。嗯,问题找到了——界面里用了太多独立的图形元素,没有做合并。后来我把几个静态元素合并成一张图片,Draw Calls 降到了 40 次,FPS 直接回到了 58。

2.1.3 它的局限性

Performance Analyzer 很好用,但它只能告诉你「整体表现如何」。它没法告诉你「具体是哪一行代码拖慢了速度」。这时候,就需要硬件定时器出马了。

注意: Performance Analyzer 在模拟器上的数据仅供参考。真机上的 CPU 架构、缓存大小、内存带宽都和 PC 不同。我建议你始终以真机数据为准。

2.2 硬件定时器打点法:精准到微秒的测量

硬件定时器打点法,说白了就是利用 MCU 内部的定时器,在代码的关键位置记录时间戳。通过计算两个时间戳的差值,你就能知道某段代码到底跑了多久。

这个方法我在项目里用过无数次。尤其是当 Performance Analyzer 显示 FPS 正常,但用户反馈「偶尔卡一下」的时候,硬件定时器几乎是唯一能抓到「罪魁祸首」的手段。

2.2.1 基本原理

大多数 MCU 都内置了 32 位或 64 位的定时器。我们只需要做三件事:

  1. 初始化一个定时器,让它以固定频率(比如 1 MHz)自由运行。
  2. 在要测量的代码段前后,分别读取定时器的当前值。
  3. 计算差值,再除以定时器频率,就得到了耗时(单位:微秒)。

代码实现非常简单。以 STM32 的 HAL 库为例:

// 初始化定时器(以 TIM2 为例,配置为 1 MHz)
void Timer_Init(void) {
    __HAL_RCC_TIM2_CLK_ENABLE();
    TIM2->PSC = 80 - 1;  // 假设主频 80 MHz,分频后为 1 MHz
    TIM2->ARR = 0xFFFFFFFF; // 最大计数值
    TIM2->CR1 |= TIM_CR1_CEN; // 启动定时器
}

// 获取当前计数值
uint32_t Timer_GetTick(void) {
    return TIM2->CNT;
}

// 使用示例
void MyRenderingFunction(void) {
    uint32_t start = Timer_GetTick();

    // ... 你要测量的代码段 ...

    uint32_t end = Timer_GetTick();
    uint32_t elapsed_us = end - start; // 单位:微秒
    // 可以将 elapsed_us 通过串口打印出来,或者存入缓冲区
}
关键点: 定时器的频率要足够高。1 MHz 对应 1 微秒的分辨率,对于大多数 UI 场景已经够用。如果你的 MCU 主频很高,可以适当提高分频系数,避免计数值溢出太快。

2.2.2 实战:测量 TouchGFX 的渲染管线

我曾经在一个项目里,发现界面切换时有明显的「黑屏」现象。Performance Analyzer 显示 FPS 正常,但就是感觉不舒服。于是,我在 TouchGFX 的渲染管线里打了几个点:

  • 点 A:进入 Screen::handleTickEvent() 时
  • 点 B:调用 HAL::beginFrame() 后
  • 点 C:调用 HAL::endFrame() 前
  • 点 D:退出 handleTickEvent() 时

通过计算 B-A、C-B、D-C 的时间差,我很快发现:问题出在 beginFrame() 到 endFrame() 之间。进一步排查,发现是某个自定义控件在每一帧都重新创建了内部缓冲区。改成复用缓冲区后,黑屏问题彻底消失。

避坑指南: 我曾经犯过一个低级错误——在中断服务函数里调用 Timer_GetTick()。结果因为中断优先级的问题,导致时间戳乱跳。记住:打点代码要放在主循环或任务上下文里,不要在中断里做测量。

2.3 两种工具的对比与选择

说了这么多,你可能想问:到底该用哪个?

我的建议是:先用 Performance Analyzer 做宏观扫描,再用硬件定时器做微观定位。

下面这张表可以帮你快速决策:

对比维度 Performance Analyzer 硬件定时器打点法
使用难度 低,开箱即用 中,需要写少量代码
测量精度 帧级(约 16.7 ms 粒度) 微秒级
定位能力 只能看整体 可以定位到函数或代码行
对系统影响 几乎无影响 极小(读取寄存器)
适用场景 快速验证、日常调试 深度优化、疑难杂症排查

2.4 本章知识体系

为了让你更直观地理解这两套工具的关系,我画了一张流程图:

性能分析工具选择流程 发现界面卡顿 使用 Performance Analyzer 看整体 FPS > 55 且 Draw Calls 正常? 是 否 可能是偶发问题 用硬件定时器抓异常帧 问题明确 用硬件定时器定位具体代码 核心原则:宏观扫描找方向,微观打点定细节

这张图的核心逻辑很简单:先用 Performance Analyzer 判断问题是否严重,如果严重,再用硬件定时器去抓「元凶」。两者配合,效率最高。

2.5 本章小结