编写高效代码(之一)
之前一直没有关注编写高效代码是问题,一直以为只要代码写的越短越高效。直到看到关于优化代码方面的文章,瞬间感觉自己知识面太窄了。还是知识不够,基本功不扎实。
关于导读,编者给出了下面这个简单的例子:
for(i=0;i<strlen(pswd);i++){ if(isNUM(pswd[i])){ j++; } }
刚开始我没看出代码有什么问题,感觉很好啊,还认为strlen()写在循环里面很厉害的样子呢,现在才知道自己的见识短浅。每循环一次程序就需要调用一次strlen函数,这样就浪费了很多资源和时间,可能在小规模的程序里面并不能感觉到什么,但是在工作中接触到的大多是上万行的程序,这样一个写法,估计雇主看到都能气死。tips_1:能调用一次的函数尽量就调用一次。
tips_2:降低数据精度
我们都知道,小数后面的位数越多,数就越精确。但是越精确的数据,所需要的bit数就越多。浮点数有单精度( single)和双精度(double)两种格式,单精度浮点数占32bit,双精度占64bit,处理单精度的数自然也快一点。在 c 中,fabsf() 是计算单精度浮点数绝对值的函数,fabs() 是计算双精度的,所以如果数据是单精度浮点数,使用fabsf() 会比fabs() 快一点。
tips_3:减少函数的使用,不要老是打断我。
函数是结构化程序设计的产物,他能是代码更加模块化,耦合性更低,重用性更高。但是,过多函数的调用会带来额外的开销,除了引起跳转外,还会产生额外的指令。这时我们可以将一些小的函数直接转换成代码。比如说这个求最小值的函数:
int min(int a,int b){ return a<b? a:b; } c = min(a,b);
不如直接写成一个语句:
c = a<b? a:b;
或者也可以将小的函数写成宏定义;如果调用的地方很多,用宏的话会使代码简洁很多:
#define min(a,b) ((a)<(b))? (a):(b) c = min(a,b);
但是如果还是嫌宏方法麻烦的话,还有一种简单的方法,就是将函数声明成内联函数 inline ,关于内联函数,就是一种小型函数,牺牲空间来节省函数调用的开销,一般用作比较小的函数,它们看起来象函数,运作起来象函数,比宏(macro)要好得多,使用时还不需要承担函数调用的开销,编译时,编译器会自动用函数体覆盖函数调用。内联函数被发明出来就是为了取代 c 中的宏。
inline int min(int a,int b){ return a<b? a:b; } c = min(a,b); //编译器会自动将代码优化成: c = a<b? a:b;
tips_4:空间换时间
程序谁都能实现,但是程序的快慢就不是谁都能实现的了。老师说个这样的比喻,老板让你和小王同时编写一个程序,你们都很快编好了,但是你的代码执行了一分钟出了结果,小王的程序一秒钟就出了结果,这时老板心里会怎么想?之前没有意识到这个问题的重要性。在四轴的飞控程序中,几秒的处理 时间差,将会使飞控效率提升一个档次,更快也更加稳定。比如下面这两个求斐波那契数列的例子:
如果用这个程序的话,简直是慢的吓人,一开始我还以为输出完了呢,但是慢慢的一点点还在输出,没想到这个程序这么慢。
#include "stdio.h" int f(int n){ if (n <= 1){ return 1; } return f(n - 1) + f(n - 2); } int main(){ int result; int i; for (i = 0; i < 40; i++) { result = f(i); printf("%d\n", result); } }
但是使用下面这个代码的话,在你还没有反应过来的时候,40个斐波那契数列就已经输出完毕了,真是不比不知道,一比吓一跳啊。
int arr[40];//使用数组将数列的值缓存起来 int f(int n){ int result; if (n <= 1){ result = 1; } else { result = arr[n - 1] + arr[n - 2]; } arr[n] = result; return result; } int main(){ int result; for (int i = 0; i < 40;i++) { result = f(i); printf("%d\n", result); } }
第二个程序中,计算 f(n) = f(n-1)+f(n-2) 时就不需要调用递归执行,直接从数组中取值相加即可。
5、少用乘法,用移位代替
定点乘法在DSP中需要两个cycle,而移位操作只要1个cycle,如果是一个数乘以 2 的N 次方,就可以用移位代替乘法。
len = len * 4;
就不如下面的好:
led = led << 2;
6、少用除法,求余
除法,求余需要消耗大量的时间,很多处理器没有相应的指令,是通过软件来实现的,尽量少用。如果要除以一个常数,如下面的浮点数除法:
f = f / 5.0;
可以将它转换位乘法:
#define cof 1.0/5 f = f*cof;
7、尽量减少分支
现在的处理器都是流水线结构,if和switch 等语句会带来跳转,而跳转会打乱流水线的正常运行,影响程序的执行效率。
比如下面这个分别给奇数和偶数赋不同的值:
for (i = 0; i < 100;i++) { if (i % 2 == 0) { a[i] = x; } else a[i] = y; }
不如写成下面这个形式更好:
for (i = 0; i < 100;i+=2) { a[i] = x; a[i + 1] = y; }
8、将最有可能的分支放在if中,而不是else中。
其实还有很多优化代码的技巧,我这里只是写出了几个我们理解,并且能用到的,其他的都太高大了,我感觉以我的能力暂时还写不出那么复杂的代码。所以暂时就记录这么多吧。