1、优化基础:为什么嵌入式代码需要优化?性能与体积的权衡。

做嵌入式开发的朋友,十有八九都遇到过这种场景:产品功能调通了,一测发现 Flash 剩 2KB,RAM 只剩 200 字节,CPU 跑在 80% 占用率。客户还催着加新功能。这时候你怎么办?

我早年做智能电表项目时就栽过这个坑。代码写完了,功能全对,结果一编译,Flash 超了 12%。项目经理盯着我看,那眼神我到现在都记得。从那以后,我养成了一个习惯——写代码之前先想清楚:这行代码到底要花多少资源?

说白了,嵌入式优化不是炫技,是生存技能。

1.1 嵌入式系统的资源约束

先看一组真实数据。我随便列几个常见 MCU 的资源规格:

MCU 型号 Flash RAM 主频 典型应用
STM32F103C8T6 64 KB 20 KB 72 MHz 工业控制
ESP32 4 MB 520 KB 240 MHz 物联网
ATmega328P 32 KB 2 KB 16 MHz Arduino 入门
GD32F303 128 KB 32 KB 120 MHz 电机驱动

你看,最常用的 STM32F103,Flash 才 64KB。一首 MP3 都装不下。RAM 更可怜,20KB 连一张 320×240 的图片都存不了。这就是我们每天面对的现实。

你想想看,在这种环境下写代码,每一行都要精打细算。PC 程序员写个循环用 std::vector 随便 push_back,嵌入式里一个动态内存分配就可能把堆撑爆。

1.2 性能与体积:鱼和熊掌

这里有个核心矛盾:代码跑得快,往往体积就大;代码体积小,往往跑得慢。

为什么会这样?我举个例子。

假设你要计算一个数的平方。最直接的方式:

// 方式 A:直接计算
int square(int x) {
    return x * x;
}

编译器可能会把它优化成一条乘法指令。体积小,速度快。

但如果你用查表法:

// 方式 B:查表法
const int lookup[256] = {0, 1, 4, 9, 16, ...};
int square(int x) {
    return lookup[x];
}

查表法快不快?快,一次内存访问就出结果。但代价呢?256 个 int 占 1KB 的 Flash。如果输入范围是 0~65535,那就要 256KB——整个芯片的 Flash 都不够用。

这就是典型的空间换时间。

核心权衡原则:

  • 时间优先:用查表、循环展开、内联函数——体积变大
  • 空间优先:用函数复用、共享代码段、紧凑数据结构——速度变慢
  • 平衡点:在满足实时性要求的前提下,尽量压缩体积

我在做车载 OBD 诊断协议栈时,就遇到过这种选择。协议栈要求响应时间小于 10ms,但 Flash 只剩 8KB。最后我选择了混合策略:高频调用的函数用查表加速,低频分支用紧凑的 if-else 链。效果不错,既满足了时序,又没超体积。

1.3 优化的三个层次

很多人一上来就盯着汇编看,其实没必要。优化的层次是有顺序的:

  1. 算法与数据结构——收益最大,往往能带来数量级的提升
  2. 编译器优化——零成本,改个编译选项就行
  3. 手写汇编/微架构优化——收益递减,只在关键路径使用

我见过最离谱的案例:有人花了两周手写汇编优化一个排序函数,结果把算法从冒泡改成快速排序,性能直接提升了 50 倍。两周 vs 两小时,你说哪个划算?

我的建议:先做算法级优化,再开编译器优化,最后才考虑手写汇编。这个顺序能帮你省下 80% 的优化时间。

1.4 什么时候必须优化?

不是所有代码都需要优化。我总结了几条判断标准:

场景 必须优化 可以暂缓
Flash 占用 > 85% ✅ 立即优化 —
CPU 占用 > 70% ✅ 需要优化 —
中断响应超时 ✅ 必须优化 —
功能原型验证 — ✅ 先跑通再说
一次性量产固件 — ✅ 可适度优化

嗯,这里要注意:不要过早优化。我早期做项目时,总喜欢在写代码时就想着怎么优化,结果代码结构搞得一团糟,后期维护成本翻倍。后来我学乖了:先跑通,再测性能,最后针对瓶颈优化。

1.5 本章知识体系

下面这张图,是我对本章核心逻辑的总结。你可以把它当作后续学习的导航图:

嵌入式代码优化知识体系 嵌入式代码优化知识体系 为什么需要优化? 资源约束 性能 vs 体积 优化三个层次 Flash 有限 RAM 紧张 CPU 主频低 时间优先 空间优先 混合策略 算法/数据结构 编译器优化 手写汇编 核心原则:先跑通 → 测性能 → 找瓶颈 → 针对性优化 不要过早优化,不要过度优化

1.6 一个真实的权衡案例

最后分享一个我亲身经历的项目。当时做一款便携式心电图仪,主控是 STM32F103,Flash 64KB,RAM 20KB。

算法团队给了一个数字滤波函数,用了 32 阶 FIR 滤波器。代码很漂亮,但问题来了:

  • 滤波器系数表占 128 字节(32 个 float)
  • 延迟线缓冲区占 128 字节
  • 每次滤波需要 32 次乘加运算

RAM 还剩 3KB,Flash 还剩 8KB。看起来够用?

但产品经理说:还要加蓝牙协议栈、LCD 驱动、按键扫描、电池管理……

我当时的做法是:

  1. 把 float 系数改成 int16 定点数——体积不变,但运算快了 3 倍
  2. 把延迟线缓冲区从全局变量改成静态局部变量——节省了 128 字节的全局 RAM
  3. 把滤波器阶数从 32 降到 16——性能损失 2dB,但 Flash 省了 64 字节,RAM 省了 64 字节

最终产品顺利量产。你看,优化不是非黑即白,而是在约束条件下找最优解。

我曾经踩过的坑:有一次为了省 4 字节 RAM,把一个标志位塞进了结构体的 padding 位。结果代码移植到新编译器后,padding 布局变了,整个模块跑飞。从那以后,我再也不敢在可移植代码里依赖位域布局。省那 4 字节,不值得。

好了,这一章的核心就这些。记住一句话:嵌入式优化的本质,是在有限的资源里,用最合适的代价满足功能需求。后面的章节,我们会一步步深入编译器优化和代码压缩的具体技术。