嵌入式代码卡到「磨洋工」?11个「提速神操作」让它直接飞起来!

你有没有过这种崩溃时刻?写的嵌入式代码在硬件上跑起来像刚睡醒的蜗牛——传感器数据延迟半秒,电机响应慢得像卡顿的玩具车,甚至关键时刻突然「掉链子」?明明逻辑查了八遍没毛病,可就是快不起来!

别怀疑自己的技术,其实不是你写得差,而是没抓住那些「藏在细节里的提速密码」。嵌入式代码的高效,从来不只靠编译器「给力」,更多时候是靠咱们手动「给代码松绑」。今天就把这些能让代码「瘦身又狂奔」的方法扒得明明白白,哪怕是刚入门的新手,也能一看就懂、一用就灵!

1、选对算法和数据结构:别让代码「走冤枉路」

选数据结构就像选交通工具——你要是想在一堆杂乱的数里频繁插删数据,用数组就像「在拥挤的公交车里挪位置」,又慢又费劲;但用链表,就像「骑共享单车」,灵活穿梭,速度直接翻倍。

还有数组和指针这对「老搭档」:数组直观,就像按门牌号找住户,一眼能看懂;但指针更灵活,像快递员直接握着手写地址,能跳过「数门牌号」的步骤。大部分编译器下,指针生成的代码更短、执行更快,尤其是多维数组,差异能明显到让你拍大腿:「原来还能这么省时间!」

比如同样是遍历数据,数组索引要每次算「t对应的地址」,而指针只要每次往前挪一步:

  • 数组索引:for(;;){ A=array[t++]; }(每次都要算「array的起始地址+ t」)
  • 指针运算:p=array; for(;;){ a=*(p++); }(p一开始就锁定地址,每次只挪1步)
    谁快谁慢,一眼就看出来了吧!

2、给变量「穿合脚的鞋」:别让它「负重跑步」

写代码别总给变量「过度配置」——能让char(字符型)干活的,别硬派int(整型)上;int能搞定的,别麻烦long int(长整型);至于float(浮点型),能不用就不用。

这不是抠门,是给硬件「减负」。你想啊,变量类型越大,占用的内存越多,CPU处理时就得「搬更多砖」,速度自然慢。但要注意:别让变量「超负荷」!比如给char变量塞超过它范围的值,编译器不报错,但程序会给你搞出「莫名其妙的bug」,查起来能让你头发掉一把,血的教训!

还有printf参数,尽量用「轻量级」的:%c(字符)、%d(整数)、%x(十六进制)这些就够了,少用%ld(长整数),尤其别碰%f(浮点)——用一次%f,生成的代码能多一大截,执行速度直接掉档,谁用谁知道!

3、减少运算「强度」:让CPU少干「重活」

别让CPU在循环里「反复加班」,很多运算都能「提前简化」,比如这几个常用技巧:

(1)查表:游戏大佬都在用的「偷懒法」

游戏程序员从不在主循环里算复杂公式——早就提前算好结果存成「表」,要用时直接查,就像吃预制菜,热一下就好,不用从头买菜切菜。

比如算阶乘,旧代码写递归函数让程序「每次都算一遍」:

long factorial(int i) { 
    if (i == 0) return 1; 
    else return i * factorial(i - 1); 
}

新代码直接查提前备好的表,速度快到飞起:

static long factorial_table[] = {112624120720 /* 提前算好 */}; 
long factorial(int i) { return factorial_table[i]; }

要是表太大,就写个初始化函数,在循环外提前生成,绝不占用主循环的「宝贵时间」!

(2)求余变位操作:1个指令周期搞定

比如a = a % 8,别让CPU调用子程序算,直接改成a = a & 7——位操作就像「直接掰二进制的小开关」,1个指令周期就能完成,比调用子程序快多了。记住:只要是求「2的n次方的余数」,都能用位操作代替!

(3)平方/立方别用pow:乘法更快

a = pow(a, 2.0)看着高级,实则是「绕远路」——pow要调用浮点子程序,而a = a * a直接用乘法,尤其是有硬件乘法器的单片机(比如51系列、AVR),乘法只要2个时钟周期,比pow快出几条街!

求3次方更明显:a = pow(a, 3.0)改成a = a*a*a,效率提升肉眼可见。

(4)乘除法用移位:比子程序快N倍

a = a * 4改成a = a << 2(左移2位),b = b / 4改成b = b >> 2(右移2位)——只要是乘/除「2的n次方」,都能用移位代替。

编译器对移位特别友好,能直接生成简单指令;但乘除其他数,就得调用子程序,又慢又占空间。比如a = a * 9,可以拆成a = (a << 3) + a(左移3位是乘8,再加a就是乘9),运算量直接减半!

(5)避免「重复除法」:乘法代替连除

整数除法是CPU的「软肋」,能少用就少用。比如m = i / j / k,别让CPU先除j再除k,改成m = i / (j * k)——一次除法搞定,前提是j*k不溢出,不然会出bug哦!

(6)用自增/自减:比赋值快一步

x = x + 1看着简单,但编译器生成的代码要「取x→加1→存x」三步;而x++(或++x)只要一步「x自增」,不用来回读写内存,速度直接快一截。

同理,x = x - 1改成x--,效果一样——CPU对自增自减有「专属指令」,不用浪费时间在「取存」上。

(7)复合赋值更高效

a += 1b -= 2这种复合赋值,编译器能生成更简洁的代码,比a = a + 1b = b - 2省空间又快,写起来还省事,何乐而不为?

(8)提取公共子表达式:别让CPU反复算

有些表达式会在多个地方重复出现,比如:

e = b * c / d; 
f = b / d * a;

这里b / d算了两次,改成提前存成临时变量:

const float t = b / d; 
e = c * t; 
f = a * t;

一次计算,两次使用,CPU直接少干一半活!同理,e = a / cf = b / c可以改成t = 1.0f / c,再用e = a * tf = b * t——除法变乘法,速度又快一步。

4、结构体成员:别让内存「有空洞」

结构体的内存布局很讲究,要是成员顺序乱排,会出现「内存空洞」(编译器为了对齐,会在成员间补空字节),既浪费空间,又拖慢访问速度。

(1)按类型长度排序:长的放前面

声明结构体时,把占内存多的类型(比如double、long)放前面,占内存少的(比如char、short)放后面。比如旧代码:

struct { 
    char a[5];   // 1字节*5=5字节
    long k;      // 4字节(编译器会补3字节对齐,凑够8字节)
    double x;    // 8字节
} baz;

改成新顺序,直接避免空洞:

struct { 
    double x;    // 8字节(先放最长的)
    long k;      // 4字节(接在后面,不用补空)
    char a[5];   // 5字节
    char pad[3]; // 补3字节,凑够8的整数倍(对齐)
} baz;

这样内存利用率拉满,CPU访问时不用「跳着读」,速度自然快。

(2)本地变量也按长度排序

声明本地变量时,也把长类型(double、long)放前面,短类型放后面。比如旧代码:

short ga, gu, gi; 
long foo, bar; 
double x, y, z[3]; 
char a, b; 
float baz;

改成:

double z[3], x, y;  // 最长的先上
long foo, bar;      // 接着是long
float baz;          // 然后是float
short ga, gu, gi;   // 最后是short

编译器按声明顺序分配内存,长类型放前面,后续变量能「连续排列」,不用补空字节,访问速度更快。

(3)频繁用的指针参数:存成本地变量

函数里要是频繁用指针参数(比如*q*r),别每次都直接访问——编译器怕指针「打架」(不知道两个指针是不是指向同一地址),不敢优化,只能反复读写内存。

比如旧代码:

void isqrt(unsigned long a, unsigned long* q, unsigned long* r) { 
    *q = a; 
    if (a > 0) { 
        while (*q > (*r = a / *q)) {  // 反复访问*q、*r
            *q = (*q + *r) >> 1; 
        } 
    } 
    *r = a - *q * *q; 
}

改成先把指针值存成本地变量,最后再写回去:

void isqrt(unsigned long a, unsigned long* q, unsigned long* r) { 
    unsigned long qq, rr;  // 本地变量存值
    qq = a; 
    if (a > 0) { 
        while (qq > (rr = a / qq)) {  // 访问本地变量,快!
            qq = (qq + rr) >> 1; 
        } 
    } 
    rr = a - qq * qq; 
    *q = qq;  // 最后写回指针
    *r = rr; 
}

本地变量存在寄存器里,访问速度比内存快N倍,代码直接提速!

5、循环优化:别让循环「拖后腿」

循环是代码里「重复最多的部分」,优化循环,相当于优化了大部分执行时间,这些技巧一定要记牢:

(1)小循环直接「展开」:别让CPU反复判断

比如4x4矩阵和矢量相乘,旧代码用嵌套循环:

for (i = 0; i < 4; i ++) { 
    r[i] = 0; 
    for (j = 0; j < 4; j ++) { 
        r[i] += M[j][i]*V[j]; 
    } 
}

改成直接展开,去掉循环判断:

r[0] = M[0][0]*V[0] + M[1][0]*V[1] + M[2][0]*V[2] + M[3][0]*V[3]; 
r[1] = M[0][1]*V[0] + M[1][1]*V[1] + M[2][1]*V[2] + M[3][1]*V[3]; 
r[2] = M[0][2]*V[0] + M[1][2]*V[1] + M[2][2]*V[2] + M[3][2]*V[3]; 
r[3] = M[0][3]*V[0] + M[1][3]*V[1] + M[2][3]*V[2] + M[3][3]*V[3];

没有循环变量判断,CPU直接「一路算到底」,速度快多了——当然,只适合小循环,大循环展开会让代码变臃肿。

(2)循环外做「初始化」:别让CPU反复干同一件事

循环里要是有「不随循环变量变的操作」(比如函数调用、数组访问、表达式计算),全提到循环外面!比如:

// 不好的写法:循环里反复算a->b->c[4]
for (i = 0; i < MAX; i++) { 
    total += a->b->c[4]->num + i; 
}

改成先把a->b->c[4]存成临时变量,再进循环:

struct data *temp = a->b->c[4];  // 循环外算一次
for (i = 0; i < MAX; i++) { 
    total += temp->num + i;  // 直接用临时变量
}

CPU不用每次都「找地址」,效率直接拉满。

(3)延时函数用「自减」:代码更短

旧的延时函数用「i从0涨到1000」:

void delay(void) { 
    unsigned int i; 
    for (i=0; i<1000; i++) ; 
}

改成「i从1000减到0」:

void delay(void) { 
    unsigned int i; 
    for (i=1000; i>0; i--) ; 
}

几乎所有编译器对自减循环更友好——因为MCU有「为0转移」指令,i减到0直接跳转,不用判断「是不是小于1000」,生成的代码能少1-3个字节,积少成多就是大提升!

(4)do…while比while快:少一次判断

比如循环1000次,while写法要先判断i<1000:

unsigned int i = 0; 
while (i < 1000) { 
    i++; 
    // 业务代码
}

do…while写法先执行一次,再判断i>0:

unsigned int i = 1000; 
do { 
    i--; 
    // 业务代码
} while (i > 0);

do…while少一次初始判断,编译后的代码更短,执行更快——尤其循环次数多的时候,差异更明显。

(5)循环嵌套「合并」:减少重复初始化

比如给二维数组赋值,旧代码分两个循环:

// 先把数组全置0
for (i = 0; i < MAX; i++) 
    for (j = 0; j < MAX; j++) 
        a[i][j] = 0.0; 
// 再把对角线置1
for (i = 0; i < MAX; i++) 
    a[i][i] = 1.0;

改成一个循环,置0后直接置对角线:

for (i = 0; i < MAX; i++) { 
    for (j = 0; j < MAX; j++) 
        a[i][j] = 0.0; 
    a[i][i] = 1.0;  // 一次循环搞定
}

少了一次循环初始化和判断,速度更快,代码还更简洁。

(6)switchcase按「频率排序」:少走冤枉路

switch语句要是用「比较链」(像if-else-if),就把「最常出现的case」放前面。比如统计每个月的天数,31天的月份最多,就把case31放最前面:

// 不好的写法:少的case放前面
switch (days_in_month) { 
    case 28: case 29: short_months++; break; 
    case 30: normal_months++; break; 
    case 31: long_months++; break; 
}

// 好的写法:多的case放前面
switch (days_in_month) { 
    case 31: long_months++; break;  // 最常出现,先判断
    case 30: normal_months++; break; 
    case 28: case 29: short_months++; break; 
}

这样CPU不用每次都先判断「是不是28/29天」,直接命中高频case,速度更快。

(7)大switch拆「嵌套」:减少比较次数

要是case特别多,就把「高频case」放外层,「低频case」放内层嵌套:

pMsg = ReceiveMessage(); 
switch (pMsg->type) { 
    // 高频消息放外层,直接判断
    case FREQUENT_MSG1: handleFrequentMsg1(); break; 
    case FREQUENT_MSG2: handleFrequentMsg2(); break; 
    // 低频消息放内层,少走流程
    default: 
        switch (pMsg->type) { 
            case INFREQUENT_MSG1: handleInfrequentMsg1(); break; 
            case INFREQUENT_MSG2: handleInfrequentMsg2(); break; 
        } 
}

高频消息不用进嵌套,低频消息才走内层,比较次数直接减少,效率更高。

(8)无限循环用for(;😉:比while(1)更省

写无限循环时,别用while (1),改成for (;;)——看编译后的代码就知道:

  • while (1):要先把1装进寄存器,判断是不是0,再跳转(多两步操作)
  • for (;;):直接跳转,没有判断和寄存器操作

虽然单次差异小,但无限循环会一直跑,积少成多就是大节省,写起来也不麻烦,何乐而不为?

6、让CPU「并行干活」:别浪费流水线

现在很多CPU都有「流水线」(比如4段浮点加法流水线),能同时处理多个运算,咱们要做的就是「拆分工活」,让流水线满负荷运行。

比如累加数组,旧代码用一个sum从头加到底:

double a[100], sum = 0.0; 
for (i=0; i<100; i++) sum += a[i];

改成4个sum同时加,最后合并结果:

double a[100], sum1=0, sum2=0, sum3=0, sum4=0, sum; 
for (i=0; i<100; i+=4) { 
    sum1 += a[i];    // 流水线1
    sum2 += a[i+1];  // 流水线2
    sum3 += a[i+2];  // 流水线3
    sum4 += a[i+3];  // 流水线4
} 
sum = (sum1+sum2) + (sum3+sum4);  // 最后合并

4个sum同时运算,相当于CPU「4只手一起干活」,速度直接翻4倍(前提是CPU支持流水线,大部分嵌入式CPU都支持)!

7、函数优化:别让函数调用「耗时间」

函数调用要「入栈、出栈」,次数多了会拖慢速度,这些技巧能减少消耗:

(1)小函数用inline:直接「嵌入」代码

在C++里,给小函数加inline关键字,编译器会把函数代码「直接嵌入调用处」,不用走「调用-返回」流程。比如:

inline int add(int a, int b) { return a + b; }

调用add(1,2)时,编译器直接换成1+2,省去了「入栈参数、跳转、出栈」的时间,小函数调用越频繁,提速越明显。

(2)不用的返回值:声明成void

要是函数返回值从来不用,别让它返回int(默认),直接声明成void——编译器不用额外处理返回值,生成的代码更短。

(3)少传参数:用全局变量(慎⽤)

函数参数多了,入栈出栈会耗时间,要是参数在多个函数里用,可以用全局变量代替——但要注意:全局变量会破坏程序模块化,还可能导致重入问题(比如中断里调用函数),非必要不用,用了一定要注释清楚!

(4)函数要有原型:给编译器「优化线索」

所有函数都要写原型(比如int add(int a, int b);),编译器能通过原型知道参数类型、返回值,从而做更多优化,比如减少类型转换、优化参数传递。

(5)本地函数加static:强制「内部连接」

要是函数只在当前文件里用,给它加static关键字,编译器会把它当成「内部函数」,不用生成外部连接的代码,还可能自动优化成inline,速度更快。

8、变量优化:让变量「住」在寄存器里

变量存哪里,速度差很多——寄存器比内存快10倍以上,这些技巧能让变量多「住」在寄存器里:

(1)局部变量用register:请求「住进」寄存器

声明局部变量时加register,比如register int i;,编译器会尽量把i放进寄存器,不用来回读写内存。尤其在循环里用register变量,速度提升明显。

但要注意:register只能用在局部变量,不能取地址(寄存器没有地址),也别给太多变量加——CPU寄存器有限,加太多编译器会忽略。

(2)别让变量「被改变」:用const

给变量加const,比如const int max = 100;,编译器知道max不会变,可能直接把它「嵌入代码」(不用分配内存),还能做更多优化,比如常量折叠(max+1直接换成101)。

(3)变量声明:集中声明、短名优先

  • 同时声明多个变量:int a, b, c;int a; int b; int c;好,编译器能更合理分配内存。
  • 变量名短一点:int i;int index_of_array;好,编译器处理更快(虽然差异很小,但习惯要养成)。
  • 循环变量提前声明:别在for里声明for (int i=0;...)(C89不支持),提前声明int i; for (i=0;...),兼容性更好,编译器优化也更方便。

9、递归:别害怕,编译器很喜欢

很多人觉得C语言里递归慢,其实不然——只要递归函数参数不多,编译器会优化递归调用,甚至把递归改成循环(尾递归优化)。比如算斐波那契数列,用递归比写嵌套循环更简洁,速度也不差。

只有当递归函数参数多、深度深,可能栈溢出时,才用循环代替——别一上来就拒绝递归,编译器比你想的更擅长优化它!

10、最后提醒:优化是「平衡的艺术」

记住:没有「绝对好」的优化,只有「适合项目」的优化。比如:

  • 循环展开能提速,但会让代码变臃肿(浪费ROM);
  • 用全局变量能少传参数,但会破坏模块化;
  • 用inline能提速,但会增加代码长度。

优化的核心不是「走极端」,而是「在速度、空间、可读性之间找平衡」——比如对实时性要求高的部分(比如中断服务函数),优先优化速度;对不常用的函数,优先保证可读性。

看完这些技巧,是不是发现之前的代码「藏了很多提速空间」?其实嵌入式代码优化不难,关键是抓住「重复执行的部分」(循环、函数调用)和「资源消耗大的操作」(除法、浮点运算),一点点改进,代码就能从「蜗牛爬」变成「起飞」!

赶紧拿自己的代码试试,说不定改完会惊呼:「原来我的代码还能这么快!」

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐