在嵌入式开发中,资源永远是稀缺的。内存不够用、运行速度慢、功耗太高...这些问题是不是经常让你头疼?
今天就来分享几个实战验证的代码优化技巧!
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 维护性
渐进优化:先优化热点代码
验证结果:优化后要测试正确性
好的优化不是炫技,而是在约束条件下找到最佳平衡点。
你在嵌入式开发中还用过哪些优化技巧?