• 正文
  • 相关推荐
申请入驻 产业图谱

嵌入式开发中,有哪些优化技巧?

09/19 10:01
733
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

嵌入式开发中,资源永远是稀缺的。内存不够用、运行速度慢、功耗太高...这些问题是不是经常让你头疼?

今天就来分享几个实战验证的代码优化技巧!

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 维护性

渐进优化:先优化热点代码

验证结果:优化后要测试正确性

好的优化不是炫技,而是在约束条件下找到最佳平衡点。

你在嵌入式开发中还用过哪些优化技巧?

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

本公众号专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!