来源 | 嵌入式大杂烩
在嵌入式开发中,资源永远是稀缺的。内存不够用、运行速度慢、功耗太高...这些问题是不是经常让你头疼?
今天就来分享几个实战验证的代码优化技巧!
1.时间效率优化
避免浮点运算
// 慢速版本:浮点运算
float calculate_voltage(int adc_value) {
return adc_value * 3.3f / 4096.0f;
}
// 快速版本:定点运算
int calculate_voltage_fast(int adc_value) {
return (adc_value * 3300) >> 12; // 除以4096用右移替代
}
减少函数调用
// 慢速版本:频繁函数调用
for(int i = 0; i < 1000; i++) {
set_led_state(i % 2);
}
// 快速版本:内联展开
for(int i = 0; i < 1000; i++) {
if(i % 2) {
GPIO_SetBits(GPIOA, GPIO_Pin_5);
} else {
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
}
}
2. 空间效率优化
选择合适的数据类型
// 浪费内存的写法
struct sensor_data {
int temperature; // 实际只需要-40到125
int humidity; // 实际只需要0到100
int pressure; // 实际只需要300到1100
};
// 节省内存的写法
struct sensor_data_optimized {
int8_t temperature; // -128到127,够用
uint8_t humidity; // 0到255,够用
uint16_t pressure; // 0到65535,够用
};
// 内存节省:从12字节降到4字节
使用联合体节省空间
// 通信协议数据包
typedefunion {
struct {
uint8_t header[4];
uint8_t cmd;
uint8_t data[32];
uint8_t checksum;
} packet;
uint8_t raw_data[38];
} comm_frame_t;
// 可以按字节访问,也可以按结构访问
comm_frame_t frame;
frame.packet.cmd = 0x01; // 结构化访问
send_data(frame.raw_data, 38); // 字节数组访问
3. 用空间换时间
场景
你需要统计一个4位数据(0x0~0xF)中有多少个1,传统方法是用循环遍历每一位。
普通写法
int count_ones_slow(unsigned char data) {
int cnt = 0;
unsigned char temp = data & 0xf;
for (int i = 0; i < 4; i++) {
if (temp & 0x01) {
cnt++;
}
temp >>= 1;
}
return cnt;
}
优化写法
// 预先计算好的查找表
static int ones_table[16] = {
0, 1, 1, 2, 1, 2, 2, 3,
1, 2, 2, 3, 2, 3, 3, 4
};
int count_ones_fast(unsigned char data) {
return ones_table[data & 0xf];
}
性能对比:
传统方法:需要4次循环 + 4次位运算 查表方法:仅需1次数组访问
适用场景:复杂计算、三角函数、CRC校验等
4. 使用柔性数组
传统指针方式的问题
typedef struct {
uint16_t head;
uint8_t id;
uint8_t type;
uint8_t length;
uint8_t *value; // 指针方式
} protocol_old_t;
问题:
需要两次内存分配 内存不连续,访问效率低 释放内存容易出错
柔性数组的优雅
typedef struct {
uint16_t head;
uint8_t id;
uint8_t type;
uint8_t length;
uint8_t value[]; // 柔性数组
} protocol_new_t;
优势:
一次分配,连续内存 访问速度更快 内存管理更简单 避免内存泄漏风险
使用示例:
// 分配结构体 + 数据空间
protocol_new_t *p = malloc(sizeof(protocol_new_t) + data_len);
5. 使用位操作
位域:节省内存的小技巧
管理8个标志位,你会怎么做?
内存浪费的写法:
struct flags_waste {
unsignedchar flag1;
unsignedchar flag2;
unsignedchar flag3;
unsignedchar flag4;
unsignedchar flag5;
unsignedchar flag6;
unsignedchar flag7;
unsignedchar flag8; // 总共8字节!
};
内存高效的写法:
struct flags_smart {
unsignedchar flag1:1; // 只用1位
unsignedchar flag2:1;
unsignedchar flag3:1;
unsignedchar flag4:1;
unsignedchar flag5:1;
unsignedchar flag6:1;
unsignedchar flag7:1;
unsignedchar flag8:1; // 总共1字节!
} flags;
内存节省:从8字节降到1字节!
位运算:替代乘除法
慢速版本:
uint32_t val = 1024;
uint32_t doubled = val * 2; // 乘法指令
uint32_t halved = val / 2; // 除法指令
快速版本:
uint32_t val = 1024;
uint32_t doubled = val << 1; // 左移1位 = 乘以2
uint32_t halved = val >> 1; // 右移1位 = 除以2
6. 循环展开 - 减少跳转
传统循环的开销
// 每次循环都有跳转开销
for (int i = 0; i < 4; i++) {
process(array[i]);
}
展开后的高效版本
// 直接执行,无跳转开销
process(array[0]);
process(array[1]);
process(array[2]);
process(array[3]);
高级展开:并行计算
普通版本:
long calc_sum_slow(int *arr0, int *arr1) {
long sum = 0;
for (int i = 0; i < 1000; i++) {
sum += arr0[i] * arr1[i]; // 串行计算
}
return sum;
}
优化版本:
long calc_sum_fast(int *arr0, int *arr1) {
long sum0 = 0, sum1 = 0, sum2 = 0, sum3 = 0;
for (int i = 0; i < 250; i += 4) {
sum0 += arr0[i+0] * arr1[i+0]; // 并行计算
sum1 += arr0[i+1] * arr1[i+1];
sum2 += arr0[i+2] * arr1[i+2];
sum3 += arr0[i+3] * arr1[i+3];
}
return sum0 + sum1 + sum2 + sum3;
}
7. 内联函数 - 消除函数调用开销
函数调用的隐藏成本
每次函数调用都有开销:
参数压栈 跳转指令 栈帧管理 返回地址保存
内联函数的魔力
static inline void toggle_led(uint8_t pin) {
PORT ^= 1 << pin;
}
// 编译后直接展开,无函数调用开销
toggle_led(LED_PIN);
适用场景:
频繁调用的小函数 关键路径上的函数 简单的工具函数
8. 数据类型优化
循环变量的学问
低效写法:
char i; // 可能溢出,编译器需要额外检查
for (i = 0; i < N; i++) {
// ...
}
高效写法:
int i; // 编译器优化更好
for (i = 0; i < N; i++) {
// ...
}
数据类型选择原则
循环索引:优先使用int 存储优化:能用char就不用int 计算密集:避免不必要的浮点运算 类型转换:减少隐式类型转换
9. 循环优化策略
多重循环的排列艺术
低效排列(长循环在外层):
for (row = 0; row < 100; row++) { // 外层100次
for (col = 0; col < 5; col++) { // 内层5次
sum += a[row][col];
}
}
// 总跳转次数:100次外层跳转
高效排列(长循环在内层):
for (col = 0; col < 5; col++) { // 外层5次
for (row = 0; row < 100; row++) { // 内层100次
sum += a[row][col];
}
}
// 总跳转次数:5次外层跳转
提前退出策略
低效方法(执行完整循环):
bool found = false;
for (int i = 0; i < 10000; i++) {
if (list[i] == target) {
found = true; // 找到了还继续循环!
}
}
高效方法(找到即退出):
bool found = false;
for (int i = 0; i < 10000; i++) {
if (list[i] == target) {
found = true;
break; // 立即退出
}
}
10. 结构体内存对齐优化
内存对齐的影响
未优化版本:
struct waste_memory {
char a; // 1字节
short b; // 2字节,需要对齐
char c; // 1字节
int d; // 4字节,需要对齐
char e; // 1字节
};
// 实际占用:16字节
优化版本:
struct save_memory {
char a; // 1字节
char c; // 1字节
short b; // 2字节,正好对齐
int d; // 4字节,正好对齐
char e; // 1字节
};
// 实际占用:12字节,节省25%!
注意事项
优化原则
不要为了优化而优化;不要牺牲代码可读性。
先测量,后优化:用工具找到真正的瓶颈 权衡取舍:性能 vs 可读性 vs 维护性 渐进优化:先优化热点代码 验证结果:优化后要测试正确性
好的优化不是炫技,而是在约束条件下找到最佳平衡点。
你在嵌入式开发中还用过哪些优化技巧?
------------ END ------------

嵌入式项目,你用现成软硬件模块,还是从0造轮子?

OSPI Flash适配秘籍之移形换位大法

单片机外部Flash下载算法原理及实现方法