嵌入式C语言之所以经久不衰,在于它的运行效率很高,想要高效运行代码,除了编译器帮忙优化,关键还要靠自己“优化”代码。
数组索引 指针运算 For(;;){ p=array A=array[t++]; for(;;){ a=*(p++); 。。。。。。。。。。。。。。。 } } 指针方法的优点是,array的地址每次装入地址p后,在每次循环中只需对p增量操作。在数组索引方法中,每次循环中都必须根据t值求数组下标的复杂运算。
long factorial(int i) { if (i == 0) return 1; else return i * factorial(i - 1); } 新代码:
static long factorial_table[] = {1, 1, 2, 6, 24, 120, 720 /* etc */ }; long factorial(int i) { return factorial_table[i]; } 如果表很大,不好写,就写一个init函数,在循环外临时生成表格。
a=a%8; 可以改为:
a=a&7; 说明:位操作只需一个指令周期即可完成,而大部分的C编译器的“%”运算均是调用子程序来完成,代码长、执行速度慢。通常,只要求是求2n方的余数,均可使用位操作的方法来代替。
a=pow(a, 2.0); 可以改为:
a=a*a; 说明:在有内置硬件乘法器的单片机中(如51系列),乘法运算比求平方运算快得多,因为浮点数的求平方是通过调用子程序来实现的,在自带硬件乘法器的AVR单片机中,如ATMega163中,乘法运算只需2个时钟周期就可以完成。既使是在没有内置硬件乘法器的AVR单片机中,乘法运算的子程序比平方运算的子程序代码短,执行速度快。 如果是求3次方,如:
a=pow(a,3.0); 更改为:
a=a*a*a; 则效率的改善更明显。
a=a*4; b=b/4; 可以改为:
a=a<<2; b=b>>2; 通常如果需要乘以或除以2n,都可以用移位的方法代替。在ICCAVR中,如果乘以2n,都可以生成左移的代码,而乘以其它的整数或除以任何数,均调用乘除法子程序。用移位的方法得到代码比调用乘除法子程序生成的代码效率高。实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果,如:
a=a*9 可以改为:
a=(a<<3)+a 采用运算量更小的表达式替换原来的表达式,下面是一个经典例子: 旧代码:
x = w % 8; y = pow(x, 2.0); z = y * 33; for (i = 0;i < MAX;i++) { h = 14 * i; printf("%d", h); } 新代码:
x = w & 7; /* 位操作比求余运算快*/ y = x * x; /* 乘法比平方运算快*/ z = (y << 5) + y; /* 位移乘法比乘法快 */ for (i = h = 0; i < MAX; i++) { h += 14; /* 加法比乘法快 */ printf("%d",h); }
int i, j, k, m; m = i / j / k; 推荐的代码:
int i, j, k, m; m = i / (j * k);
x=x+1; 模仿大多数微机汇编语言为例,产生的代码类似于:
move A,x ;把x从内存取出存入累加器A add A,1 ;累加器A加1 store x ;把新值存回x 如果使用增量操作符,生成的代码如下:
incr x ;x加1 显然,不用取指令和存指令,增、减量操作执行的速度加快,同时长度也缩短了。
float a, b, c, d, e, f; 。。。 e = b * c / d; f = b / d * a; 推荐的代码:
float a, b, c, d, e, f; 。。。 const float t(b / d); e = c * t; f = a * t; 不好的代码:
float a, b, c, e, f; 。。。 e = a / c; f = b / c; 推荐的代码:
float a, b, c, e, f; 。。。 const float t(1.0f / c); e = a * t; f = b * t;
struct { char a[5]; long k; double x; } baz; 推荐的代码,新的顺序并手动填充了几个字节:
struct { double x; long k; char a[5]; char pad[7]; } baz; 这个规则同样适用于类的成员的布局。
short ga, gu, gi; long foo, bar; double x, y, z[3]; char a, b; float baz; 推荐的代码,改进的顺序
double z[3]; double x, y; long foo, bar; float baz; short ga, gu, gi;
// 假设 q != r void isqrt(unsigned long a, unsigned long* q, unsigned long* r) { *q = a; if (a > 0) { while (*q > (*r = a / *q)) { *q = (*q + *r) >> 1; } } *r = a - *q * *q; } 推荐的代码:
// 假设 q != r 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; }
// 3D转化:把矢量 V 和 4x4 矩阵 M 相乘 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];
void delay (void) { unsigned int i; for (i=0;i<1000;i++) ; } 将其改为自减延时函数:
void delay (void) { unsigned int i; for (i=1000;i>0;i--) ; } 两个函数的延时效果相似,但几乎所有的C编译对后一种函数生成的代码均比前一种代码少1~3个字节,因为几乎所有的MCU均有为0转移的指令,采用后一种方式能够生成这类指令。在使用while循环时也一样,使用自减指令控制循环会比使用自加指令控制循环生成的代码更少1~3个字母。但是在循环中有通过循环变量“i”读写数组的指令时,使用预减循环有可能使数组超界,要引起注意。
unsigned int i; i=0; while (i<1000) { i++; //用户程序 } 或:
unsigned int i; i=1000; do { i--; //用户程序 } while (i>0); 在这两种循环中,使用do…while循环编译后生成的代码的长度短于while循环。
for (i = 0; i < 100; i++) { do_stuff(i); } 新代码:
for (i = 0; i < 100; ) { do_stuff(i); i++; do_stuff(i); i++; do_stuff(i); i++; do_stuff(i); i++; do_stuff(i); i++; do_stuff(i); i++; do_stuff(i); i++; do_stuff(i); i++; do_stuff(i); i++; do_stuff(i); i++; } 可以看出,新代码里比较指令由100次降低为10次,循环时间节约了90%。不过注意:对于中间变量或结果被更改的循环,编译程序往往拒绝展开,(怕担责任呗),这时候就需要你自己来做展开工作了。 还有一点请注意,在有内部指令cache的CPU上(如MMX芯片),因为循环展开的代码很大,往往cache溢出,这时展开的代码会频繁地在CPU 的cache和内存之间调来调去,又因为cache速度很高,所以此时循环展开反而会变慢。还有就是循环展开会影响矢量运算优化。
for (i = 0; i < MAX; i++) /* initialize 2d array to 0's */ for (j = 0; j < MAX; j++) a[i][j] = 0.0; for (i = 0; i < MAX; i++) /* put 1's along the diagonal */ a[i][i] = 1.0; 新代码:
for (i = 0; i < MAX; i++) /* initialize 2d array to 0's */ { for (j = 0; j < MAX; j++) a[i][j] = 0.0; a[i][i] = 1.0; /* put 1's along the diagonal */ }
int days_in_month, short_months, normal_months, long_months; 。。。。。。 switch (days_in_month) { case 28: case 29: short_months ++; break; case 30: normal_months ++; break; case 31: long_months ++; break; default: cout << "month has fewer than 28 or more than 31 days" << endl; break; } 推荐的代码:
int days_in_month, short_months, normal_months, long_months; 。。。。。。 switch (days_in_month) { case 31: long_months ++; break; case 30: normal_months ++; break; case 28: case 29: short_months ++; break; default: cout << "month has fewer than 28 or more than 31 days" << endl; break; }
pMsg=ReceiveMessage(); switch (pMsg->type) { case FREQUENT_MSG1: handleFrequentMsg(); break; case FREQUENT_MSG2: handleFrequentMsg2(); break; 。。。。。。 case FREQUENT_MSGn: handleFrequentMsgn(); break; default: //嵌套部分用来处理不经常发生的消息 switch (pMsg->type) { case INFREQUENT_MSG1: handleInfrequentMsg1(); break; case INFREQUENT_MSG2: handleInfrequentMsg2(); break; 。。。。。。 case INFREQUENT_MSGm: handleInfrequentMsgm(); break; } } 如果switch中每一种情况下都有很多的工作要做,那么把整个switch语句用一个指向函数指针的表来替换会更加有效,比如下面的switch语句,有三种情况:
enum MsgType{Msg1, Msg2, Msg3} switch (ReceiveMessage() { case Msg1; 。。。。。。 case Msg2; 。。。。。 case Msg3; 。。。。。 } 为了提高执行速度,用下面这段代码来替换这个上面的switch语句。
/*准备工作*/ int handleMsg1(void); int handleMsg2(void); int handleMsg3(void); /*创建一个函数指针数组*/ int (*MsgFunction [])()={handleMsg1, handleMsg2, handleMsg3}; /*用下面这行更有效的代码来替换switch语句*/ status=MsgFunction[ReceiveMessage()]();
for (i = 1; i <= MAX; i++) { 。。。 } 新代码:
i = MAX+1; while (--i) { 。。。 } 不过千万注意,如果指针操作使用了i值,这种方法可能引起指针越界的严重错误(i = MAX+1;)。当然你可以通过对i做加减运算来纠正,但是这样就起不到加速的作用,除非类似于以下情况: 旧代码:
char a[MAX+5]; for (i = 1; i <= MAX; i++) { *(a+i+4)=0; } 新代码:
i = MAX+1; while (--i) { *(a+i+4)=0; }
for( i 。。。) { if( CONSTANT0 ) { DoWork0( i );// 假设这里不改变CONSTANT0的值 } else { DoWork1( i );// 假设这里不改变CONSTANT0的值 } } 推荐的代码:
if( CONSTANT0 ) { for( i 。。。) { DoWork0( i ); } } else { for( i 。。。) { DoWork1( i ); } } 如果已经知道if()的值,这样可以避免重复计算。虽然不好的代码中的分支可以简单地预测,但是由于推荐的代码在进入循环前分支已经确定,就可以减少对分支预测的依赖。
while (1)
和for (;;)
。这两种方法效果完全一样,但那一种更好呢?然我们看看它们编译后的代码:
编译前:while (1); 编译后:
mov eax,1 test eax,eax je foo+23h jmp foo+18h 编译前:
for (;;); 编译后:
jmp foo+23h 显然,
for (;;)
指令少,不占用寄存器,而且没有判断、跳转,比while (1)
好。double a[100], sum; int i; sum = 0.0f; for (i=0;i<100;i++) sum += a[i]; 推荐的代码:
double a[100], sum1, sum2, sum3, sum4, sum; int i; sum1 = sum2 = sum3 = sum4 = 0.0; for (i = 0;i < 100;i += 4) { sum1 += a[i]; sum2 += a[i+1]; sum3 += a[i+2]; sum4 += a[i+3]; } sum = (sum4+sum3)+(sum1+sum2); 要注意的是:使用4路分解是因为这样使用了4段流水线浮点加法,浮点加法的每一个段占用一个时钟周期,保证了最大的资源利用率。
float x[VECLEN], y[VECLEN], z[VECLEN]; 。。。。。。 for (unsigned int k = 1;k < VECLEN;k ++) { x[k] = x[k-1] + y[k]; } for (k = 1;k
total = a->b->c[4]->aardvark + a->b->c[4]->baboon + a->b->c[4]->cheetah + a->b->c[4]->dog; 新代码:
struct animals * temp = a->b->c[4]; total = temp->aardvark + temp->baboon + temp->cheetah + temp->dog; 一些老的C语言编译器不做聚合优化,而符合ANSI规范的新的编译器可以自动完成这个优化,看例子:
float a, b, c, d, f, g; 。。。 a = b / c * d; f = b * g / c; 这种写法当然要得,但是没有优化
float a, b, c, d, f, g; 。。。 a = b / c * d; f = b / c * g; 如果这么写的话,一个符合ANSI规范的新的编译器可以只计算b/c一次,然后将结果代入第二个式子,节约了一次除法运算。
a = b(); c(&d); 因为d的地址被c函数使用,有可能被改变,编译器不敢把它长时间的放在寄存器里,一旦运行到c(&d),编译器就把它放回内存,如果在循环里,会造成N次频繁的在内存和寄存器之间读写d的动作,众所周知,CPU在系统总线上的读写速度慢得很。比如你的赛杨300,CPU主频300,总线速度最多66M,为了一个总线读,CPU可能要等4-5个周期,得。。得。。得。。想起来都打颤。
全部0条评论
快来发表一下你的评论吧 !