嵌入式开发中几种实用的优化技巧

strongerHuang 2025-09-23 08:00

来源 | 嵌入式大杂烩


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

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

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] = {
    01121223
    12232334
};

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%!

注意事项

优化原则

不要为了优化而优化;不要牺牲代码可读性。

  1. 先测量,后优化:用工具找到真正的瓶颈
  2. 权衡取舍:性能 vs 可读性 vs 维护性
  3. 渐进优化:先优化热点代码
  4. 验证结果:优化后要测试正确性

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

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


------------ END ------------


资讯配图

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


资讯配图

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


资讯配图

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


声明:内容取材于网络,仅代表作者观点,如有内容违规问题,请联系处理。 
嵌入式
more
嵌入式软件的解耦
电子与嵌入式技术年度大展,观众登记免费参展!
STM32嵌入式开发项目实战|RTOS+MQTT+阿里云平台全流程教学+1V1辅导
告别NOR+EEPROM?RRAM正在重塑嵌入式外存格局
下周二开幕|《观展攻略》一键收藏!深圳国际电子展暨嵌入式展8月26-28日福田会展中心盛大启幕!
嵌入式软件开发中的不良习惯!
PCIM2025论文摘要 | 基于英飞凌S-cell产品的嵌入式PCB方案在主驱逆变器应用的优势分析与研究
嵌入式代码参数管理框架(C语言)
从云端到终端,AI正加速“落地”!AI盛会揭秘嵌入式智能落地新范式
嵌入式老司机带你解锁 C 语言状态机的正确姿势
Copyright © 2025 成都区角科技有限公司
蜀ICP备2025143415号-1
  
川公网安备51015602001305号