7.3 性能优化与资源管理:三本预算表


7.3 性能优化与资源管理:三本预算表

本节摘要:性能优化的专业姿态不是"哪里慢优化哪里",而是先编预算表、再按偏差优化:时间预算论证响应能力,内存预算防住资源危机,功耗预算兑现电池寿命。本节给出三本预算表的编制方法、每本账的典型优化手段与优先级排序,并用一个真实项目演示从预算超支到收敛的完整过程。

优化前先立规矩:预算思维三原则

嵌入式系统的资源是硬约束——Flash 就那么大,主频就那么高,电池就那颗。预算思维的三原则由此而来。原则一:最坏情况记账。演示看平均,产品看最坏:响应时间按"最长 ISR 加最深嵌套"算,内存按"最深调用加最大并发"算,2.4 节的功耗账已经是这个算法。原则二:先测量后优化。直觉式优化十次有九次砍错树——先量出每段的真实耗时与每个区域的真实占用,再决定砍哪里。原则三:留 20% 余量。预算打满的设计在第一次需求变更时就会崩,余量是给未来留的活路。

第一本账:时间预算

时间预算回答"系统跟得上吗"。编制方法分四步:列出所有周期性任务与其周期(按键扫描 20ms、控制环 1ms、上报 1s);列出所有中断及其最坏执行时间(打点法实测,5.4 节的方法);算 CPU 占用率——各任务的"最坏执行时间除以周期"求和,经验红线是 70%(留出中断、抖动与未来的余量);对有硬性截止期的关键链路(中断响应到输出动作)单独画链路账,累加每一环的最坏耗时。

/* 时间预算的测量底座:节拍计数与打点 */ volatile uint32_t g_ticks; /* 1ms 系统节拍 */ uint32_t profile_start, profile_cost; /* 打点法测耗时 */ profile_start = g_ticks; /* 粗粒度:毫秒级 */ do_control_loop(); profile_cost = g_ticks - profile_start; /* 细粒度用调试引脚:进函数拉高 出函数拉低,示波器量 */ #define PIN_HIGH() (GPIOB->BSRR = (1 << 5)) #define PIN_LOW() (GPIOB->BSRR = (1 << (5 + 16)))

优化的优先级排序有讲究。第一优先:算法与频次——把 1ms 才需要做的事放到 10ms 做,收益是十倍级的且无风险;第二优先:搬家——重活从 ISR 搬进任务,从 CPU 搬给外设(DMA、硬件滤波);第三优先:编译器优化等级与查表代替计算(4.5 的 gamma 表、7.1 的校准曲线都是查表思维的实例);最后才轮到手工微调代码(寄存器变量、循环展开)——收益小、可读性代价大,穷尽前三层之前别碰。

第二本账:内存预算

内存预算回答"装得下吗、撑得住吗"。三张子表:Flash 表(代码段、常量、各中间件的实际占用,链接器报告直接给出,优化手段是功能裁剪与优化等级);RAM 静态表(全局与静态变量、各任务栈、各缓冲区,累加后与总容量对照);栈水位实测表——这张最要紧,2.2 节的"栈踩内存"案例已经演示过栈溢出的杀伤力,实测方法是把栈空间预先填充固定字节(如 0xA5),跑完最重负载路径后检查填充残留,得出栈的真实最深用量,再乘 1.5 倍做定值。

RAM 预算示例(32KB RAM 的项目) 预算 实测 占比
全局与静态变量 6KB 4.2KB 13%
主循环栈 2KB 实测 0.9KB,定值 1.5KB 5%
通信任务栈 1.5KB 实测 0.6KB,定值 1KB 3%
串口收发缓冲 2KB 2KB 6%
协议与日志缓冲 3KB 2.5KB 8%
RTOS 内核与空闲 2KB 2KB 6%
余量 15.5KB —— 48%

这张表的读法:占比合计没到 70% 前,内存不是瓶颈,别为省几百字节牺牲可读性;一旦逼近,按"缓冲区减配、任务栈按实测重定、最后才是算法重构"的顺序收敛。堆的问题在第 2.2 节已经裁决过——嵌入式尽量不用,预算表里自然也没有堆的位置。

第三本账:功耗预算

功耗预算回答"电池撑得到承诺吗"。方法在 2.4 节已经完整示范(电流账:各状态电流乘时长累加,除以电池容量再打折),本节补充预算视角的两个要点。其一,预算要分摊到模块:整机平均电流目标 50µA,就拆成 MCU 均摊 20µA、传感器均摊 15µA、无线均摊 15µA——超支时立刻知道拧哪个旋钮。其二,验证要上功耗实测:预算表算得再好,也要用采样电阻加示波器或功耗分析仪量一次真实电流曲线,对照预算找偏差——常见偏差源是"以为睡了其实没睡"(某外设时钟没关、引脚配置漏了),实测曲线会直接把"没睡的时间段"亮出来。

完整案例:一笔预算超支的收敛过程

背景:某穿戴设备样机,电池寿命实测只有承诺值的一半——平均电流 240µA,预算 120µA,超支一倍。操作:按三本账逐层排查。功耗实测曲线显示两个异常:每 10 秒有一个 80mA 持续 200ms 的大电流尖峰(无线模块的搜网动作),以及 MCU 几乎没有回到停机态的时间段。解读与收敛:搜网尖峰是大头——把上报间隔从 10 秒放宽到 60 秒(需求侧谈判),并把搜网策略从"全频段扫描"改为"记住上次频点优先尝试"(每次省六成时间),尖峰摊薄后均摊电流降 90µA;"没睡"问题根因是调试串口时钟未关加一个引脚悬空,修复后停机电流从 400µA 降到 6µA,均摊再降 40µA。两轮合计降到 105µA,达标并留 12% 余量。整个过程没有一行"性能优化代码"——预算思维优化的常常是需求参数与配置疏漏,而不是算法。变式:若三本账都收敛后仍不达标,那就是选型错了——预算先于选型,反过来验证了第 1.4 节"选型前先算账"的方法论。

优化的纪律:三不优化原则

预算思维还有个常被忽视的推论——知道什么时候优化。立三条纪律。一,没量过不优化:直觉指认的热点十有八九不是真热点(人总是怀疑慢语句,而真凶常是意外的函数调用与中断抖动),先测后改是硬纪律。二,没超支不优化:预算表显示 CPU 占用 35%、内存余量过半、功耗达标——那就不动。提前优化买来的是难懂的代码与引入新缺陷的机会,还堵死了后续需求的空间(精简过的代码最难再塞功能)。三,影响可读性须过门槛:任何用可读性换性能的手法(手工展开循环、查表替代计算、汇编热点),必须拿实测数字证明收益达到量级差(十倍起),并在注释里留好"优化前版本去哪找"的路标。性能是预算表上的一个数字,不是工程师的荣誉勋章——预算达标的项目里,朴素可读的代码就是最优代码

问题:预算表该在项目什么阶段建?

建两次。选型前建"纸面版":用数据手册参数与需求推算三本账的初步数字,它的作用是排除明显不行的芯片——这比"买回来试"便宜两个数量级(呼应 1.4 选型)。集成完成后建"实测版":逐项替换成实测值,与纸面版对照,差异超过两成的条目必须解释(不是参数错了就是有漏算的消耗源)。此后预算表转为回归工具:每次大改动后重测关键项,账目劣化在合并前拦截。两份表的差距本身就是学习材料——它量出的是你"从参数表到真实电路"的推算误差,做三个项目后误差会收敛到一成以内,那时你的纸面预算就有了直接指导选型的资格。

本节要点回顾

  • 预算三原则:最坏情况记账、先测量后优化、留 20% 余量——预算思维先于一切优化技巧。
  • 时间账四步:列任务周期、实测 ISR 最坏耗时、算 CPU 占用(红线 70%)、关键链路单独累加。
  • 优化四层优先级:调频次、搬家(ISR 到任务、CPU 到外设)、查表与编译选项、最后才手工微调。
  • 内存账核心是栈水位:填充法实测最深用量再乘 1.5 定值;占比 70% 以内别为字节牺牲可读性。
  • 功耗账落到模块:分摊配额让超支可定位;实测曲线专治"以为睡了其实没睡"。
  • 体系位置:产品在自己的三本账里收敛了,下一章把视野抬到行业——生态、安全与前沿。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U