第二章 性能指标与度量:响应时间、吞吐量、功耗、实时性、资源利用率

各位工程师朋友,咱们今天聊聊性能指标。说实话,我刚入行那会儿,觉得性能优化就是「把代码写快一点」。后来在理疗仪项目上栽了跟头,才明白——没有度量,就没有优化。你连指标都定义不清楚,怎么知道改得好不好?

2.1 响应时间:用户感知的「第一道坎」

响应时间,说白了就是从用户发出指令到系统给出反馈的时间。在理疗仪上,比如用户按下「开始治疗」按钮,到电极片真正输出脉冲,这中间的延迟就是响应时间。

我个人习惯把响应时间拆成三段:

  • 输入延迟:按键消抖、触摸屏采样、信号传输
  • 处理延迟:MCU执行控制算法、状态机切换
  • 输出延迟:DAC转换、功率放大、电极响应

测量方法其实不复杂。我常用的是GPIO翻转法——在任务开始前拉高一个引脚,任务结束后拉低,然后用示波器抓这个脉冲宽度。代码示例如下:

// 响应时间测量示例
void start_treatment(void) {
    GPIO_SetPin(HIGH);  // 开始计时
    // 执行治疗启动流程
    dac_set_frequency(50);   // 设置50Hz
    dac_set_amplitude(30);   // 设置30mA
    power_amp_enable(TRUE);  // 开启功放
    GPIO_SetPin(LOW);   // 结束计时
}

嗯,这里要注意:示波器的采样率至少要10倍于被测信号频率。我曾经用1M采样率的示波器去测100kHz的PWM响应,结果波形全是锯齿——白忙活一场。

我的小技巧:如果手头没有示波器,可以用逻辑分析仪。设置一个触发条件,抓取两个GPIO事件的时间差,精度也能到微秒级。

2.2 吞吐量:系统能「扛」多少活

吞吐量,就是单位时间内系统能处理的任务数量。在理疗仪上,我关心的是每秒能处理多少个治疗参数更新请求

举个例子:一台多通道理疗仪,同时给8个通道输出不同波形。每个通道每毫秒需要更新一次DAC值。那吞吐量就是:

吞吐量 = 通道数 × 更新频率 = 8 × 1000 = 8000 次/秒

测量方法我推荐两种:

  • 计数器法:在任务循环里放一个计数器,每秒打印一次数值
  • DMA统计法:利用DMA传输完成中断,统计单位时间内的传输次数

我记得有一次,客户反馈理疗仪在连续工作2小时后,波形开始抖动。排查下来,是DMA缓冲区溢出导致吞吐量下降。后来加了双缓冲机制,问题就解决了。

避坑指南:吞吐量不是越高越好。我曾经为了追求吞吐量,把中断优先级设得特别高,结果主循环饿死了,系统直接死机。吞吐量和稳定性要平衡。

2.3 功耗:电池续航的「命门」

功耗指标,在便携式理疗仪上简直是生死线。用户可不想用半小时就得充电。我一般关注三个功耗状态:

状态 典型功耗 测量方法
待机 < 50 μA 串联精密电阻测压降
运行 50 - 200 mA 电流探头 + 示波器
峰值 < 500 mA 功率分析仪

测量功耗,我有个习惯:不要只看平均功耗,要看瞬时功耗曲线。因为理疗仪的脉冲输出是间歇性的,平均功耗可能很低,但脉冲瞬间的电流尖峰能把电池电压拉低,导致MCU复位。

具体做法:用电流探头夹在电源线上,示波器设置成单次触发模式,抓取治疗启动瞬间的电流波形。如果看到超过500mA的尖峰,就得考虑加缓启动电路了。

2.4 实时性:硬实时 vs 软实时

实时性,说白了就是「系统能不能在规定时间内完成任务」。理疗仪属于硬实时系统——如果电极脉冲的时序偏差超过1ms,患者可能会感到刺痛,甚至灼伤。

我常用的实时性指标有两个:

  • 最差情况执行时间(WCET):代码在最坏路径下的执行时间
  • 抖动(Jitter):实际执行时间与理想时间的偏差

测量WCET,我推荐用周期精确的仿真器。如果没有,就用逻辑分析仪抓GPIO波形,统计1000次执行时间的最大值。代码示例:

// 测量WCET
uint32_t max_time = 0;
for (int i = 0; i < 1000; i++) {
    uint32_t start = get_cycle_count();
    generate_pulse();  // 待测函数
    uint32_t elapsed = get_cycle_count() - start;
    if (elapsed > max_time) max_time = elapsed;
}
printf("WCET: %lu cycles\n", max_time);

嗯,这里有个坑:中断会污染测量结果。我建议在测量前关闭所有可屏蔽中断,或者用DWT(数据观察点与跟踪)模块来测量,它不受中断影响。

关键认知:实时性不是「快」,而是「可预测」。一个稳定的10ms响应,比一个有时1ms有时20ms的响应更可靠。

2.5 资源利用率:别让MCU「空转」

资源利用率,包括CPU占用率、内存使用率、总线带宽等。我见过太多项目,CPU跑到99%,结果一个按键中断进来,响应延迟了50ms。

测量CPU占用率,我有个土办法:

// CPU占用率测量
uint32_t idle_count = 0;
void idle_task(void) {
    idle_count++;
}
void main_loop(void) {
    uint32_t start = idle_count;
    delay_ms(1000);  // 等1秒
    uint32_t usage = 100 - (idle_count - start) * 100 / 1000;
    printf("CPU占用率: %d%%\n", usage);
}

内存使用率,我习惯在链接脚本里定义内存区域,然后在代码里打印栈顶指针和堆指针的差值。如果发现内存使用率超过70%,就得警惕碎片问题了。

我曾经在一个项目里,内存使用率只有40%,但运行3小时后突然死机。查了两天才发现是内存碎片导致的分配失败。从那以后,我坚持用静态分配,或者用内存池管理。

我的建议:资源利用率不要超过70%。留出30%的余量,给未来的功能扩展和突发负载。这是用血泪换来的经验。

2.6 综合度量:一个实际案例

最后,我分享一个真实案例。某款理疗仪,客户反馈「治疗时感觉忽强忽弱」。我们测量了以下指标:

  • 响应时间:从按键到输出,平均12ms,但最大达到35ms
  • 吞吐量:每秒处理1200次参数更新,但DMA有溢出
  • 功耗:平均80mA,但脉冲瞬间达到450mA
  • 实时性:脉冲周期抖动达到±2ms
  • CPU占用率:85%,中断响应延迟严重

问题根源:CPU占用率过高,导致中断响应不及时,脉冲时序抖动。解决方案:把波形生成任务移到DMA + 定时器硬件自动完成,CPU只做参数计算。优化后,CPU占用率降到30%,抖动降到±0.1ms,问题解决。

你看,性能优化不是单点突破,而是系统级的平衡。每个指标都像木桶的一块板,最短的那块决定了系统的整体表现。

下一章,咱们聊聊「性能建模与分析方法」,到时候我会分享怎么用数学模型提前预测性能瓶颈。今天就到这儿,有问题随时交流。