第二章 性能指标与度量:响应时间、吞吐量、功耗、实时性、资源利用率
各位工程师朋友,咱们今天聊聊性能指标。说实话,我刚入行那会儿,觉得性能优化就是「把代码写快一点」。后来在理疗仪项目上栽了跟头,才明白——没有度量,就没有优化。你连指标都定义不清楚,怎么知道改得好不好?
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响应,结果波形全是锯齿——白忙活一场。
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(数据观察点与跟踪)模块来测量,它不受中断影响。
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小时后突然死机。查了两天才发现是内存碎片导致的分配失败。从那以后,我坚持用静态分配,或者用内存池管理。
2.6 综合度量:一个实际案例
最后,我分享一个真实案例。某款理疗仪,客户反馈「治疗时感觉忽强忽弱」。我们测量了以下指标:
- 响应时间:从按键到输出,平均12ms,但最大达到35ms
- 吞吐量:每秒处理1200次参数更新,但DMA有溢出
- 功耗:平均80mA,但脉冲瞬间达到450mA
- 实时性:脉冲周期抖动达到±2ms
- CPU占用率:85%,中断响应延迟严重
问题根源:CPU占用率过高,导致中断响应不及时,脉冲时序抖动。解决方案:把波形生成任务移到DMA + 定时器硬件自动完成,CPU只做参数计算。优化后,CPU占用率降到30%,抖动降到±0.1ms,问题解决。
你看,性能优化不是单点突破,而是系统级的平衡。每个指标都像木桶的一块板,最短的那块决定了系统的整体表现。
下一章,咱们聊聊「性能建模与分析方法」,到时候我会分享怎么用数学模型提前预测性能瓶颈。今天就到这儿,有问题随时交流。