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); }
(5)避免不必要的整数除法
整数除法是整数运算中最慢的,所以应该尽可能避免。一种可能减少整数除法的地方是连除,这里除法可以由乘法代替。这个替换的副作用是有可能在算乘积时会溢出,所以只能在一定范围的除法中使用。不好的代码:int i, j, k, m; m = i / j / k;推荐的代码:int i, j, k, m; m = i / (j * k);
(6)使用增量和减量操作符
在使用到加一和减一操作时尽量使用增量和减量操作符,因为增量符语句比赋值语句更快,原因在于对大多数CPU来说,对内存字的增、减量操作不必明显地使用取内存和写内存的指令,比如下面这条语句:x=x+1;模仿大多数微机汇编语言为例,产生的代码类似于:move A,x ;把x从内存取出存入累加器A add A,1 ;累加器A加1 store x ;把新值存回x如果使用增量操作符,生成的代码如下:incr x ;x加1显然,不用取指令和存指令,增、减量操作执行的速度加快,同时长度也缩短了。
(7)使用复合赋值表达式
复合赋值表达式(如a-=1及a+=1等)都能够生成高质量的程序代码。
(8)提取公共的子表达式
在某些情况下,C++编译器不能从浮点表达式中提出公共的子表达式,因为这意味着相当于对表达式重新排序。需要特别指出的是,编译器在提取公共子表达式前不能按照代数的等价关系重新安排表达式。这时,程序员要手动地提出公共的子表达式(在VC.NET里有一项“全局优化”选项可以完成此工作,但效果就不得而知了)。不好的代码: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;
通常使用的延时函数均采用自加的形式: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”读写数组的指令时,使用预减循环有可能使数组超界,要引起注意。
(4)while循环和do…while循环
用while循环时有以下两种循环形式: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 < 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 */ }
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; }
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; }
(8)将大的switch语句转为嵌套switch语句
当switch语句中的case标号很多时,为了减少比较的次数,明智的做法是把大switch语句转为嵌套switch语句。把发生频率高的case 标号放在一个switch语句中,并且是嵌套switch语句的最外层,发生相对频率相对低的case标号放在另一个switch语句中。比如,下面的程序段把相对发生频率低的情况放在缺省的case标号内。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()]();
(9)循环转置
有些机器对JNZ(为0转移)有特别的指令处理,速度非常快,如果你的循环对方向不敏感,可以由大向小循环。旧代码: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()中包含不变的if()):for( i 。。。) { if( CONSTANT0 ) { DoWork0( i );// 假设这里不改变CONSTANT0的值 } else { DoWork1( i );// 假设这里不改变CONSTANT0的值 } }推荐的代码:if( CONSTANT0 ) { for( i 。。。) { DoWork0( i ); } } else { for( i 。。。) { DoWork1( i ); } }如果已经知道if()的值,这样可以避免重复计算。虽然不好的代码中的分支可以简单地预测,但是由于推荐的代码在进入循环前分支已经确定,就可以减少对分支预测的依赖。
(12)选择好的无限循环
在编程中,我们常常需要用到无限循环,常用的两种方法是while (1)和for (;;)。这两种方法效果完全一样,但那一种更好呢?然我们看看它们编译后的代码:编译前:while (1);编译后:mov eax,1 test eax,eax je foo+23h jmp foo+18h编译前:for (;;);编译后:jmp foo+23h显然,for (;;)指令少,不占用寄存器,而且没有判断、跳转,比while (1)好。
6、提高CPU的并行性
(1)使用并行代码
尽可能把长的有依赖的代码链分解成几个可以在流水线执行单元中并行执行的没有依赖的代码链。很多高级语言,包括C++,并不对产生的浮点表达式重新排序,因为那是一个相当复杂的过程。需要注意的是,重排序的代码和原来的代码在代码上一致并不等价于计算结果一致,因为浮点操作缺乏精确度。在一些情况下,这些优化可能导致意料之外的结果。幸运的是,在大部分情况下,最后结果可能只有最不重要的位(即最低位)是错误的。不好的代码: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;
推荐的代码:float x[VECLEN], y[VECLEN], z[VECLEN]; 。。。。。。 float t(x[0]); for (unsigned int k = 1;k < VECLEN;k ++) { t = t + y[k]; x[k] = t; } t = x[0]; for (k = 1;k <;VECLEN;k ++) { t = z[k] * (y[k] – t); x[k] = t; }
7、循环不变计算
对于一些不需要循环变量参加运算的计算任务可以把它们放到循环外面,现在许多编译器还是能自己干这件事,不过对于中间使用了变量的算式它们就不敢动了,所以很多情况下你还得自己干。对于那些在循环中调用的函数,凡是没必要执行多次的操作通通提出来,放到一个init函数里,循环前调用。另外尽量减少喂食次数,没必要的话尽量不给它传参,需要循环变量的话让它自己建立一个静态循环变量自己累加,速度会快一点。还有就是结构体访问,东楼的经验,凡是在循环里对一个结构体的两个以上的元素执行了访问,就有必要建立中间变量了(结构这样,那C++的对象呢?想想看),看下面的例子:旧代码: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一次,然后将结果代入第二个式子,节约了一次除法运算。