6、提高CPU的并行性
( 1)使用并行代码
尽可能把长的有依赖的代码链分解成几个可以在流水线执行单元中并行执行的没有依赖的代码链。很多高级语言,包括 C++ ,并不对产生的浮点表达式重新排序,因为那是一个相当复杂的过程。需要注意的是,重排序的代码和原来的代码在代码上一致并不等价于计算结果一致,因为浮点操作缺乏精确度。在一些情况下,这些优化可能导致意料之外的结果。幸运的是,在大部分情况下,最后结果可能只有最不重要的位(即最低位)是错误的。
不好的代码:
double a[100] , sum ;
int i ;
sum = 0.0f ;
for (i=0 ; i<100 ; i++)
sum += a ;
推荐的代码:
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 ;
sum2 += a[i+1] ;
sum3 += a[i+2] ;
sum4 += a[i+3] ;
}
sum = (sum4+sum3)+(sum1+sum2) ;
要注意的是: 使用 4 路分解是因为这样使用了 4 段流水线浮点加法,浮点加法的每一个段占用一个时钟周期,保证了最大的资源利用率。
( 2)避免没有必要的读写依赖
当数据保存到内存时存在读写依赖,即数据必须在正确写入后才能再次读取。虽然 AMD Athlon 等 CPU 有加速读写依赖延迟的硬件,允许在要保存的数据被写入内存前读取出来,但是,如果避免了读写依赖并把数据保存在内部寄存器中,速度会更快。在一段很长的又互相依赖的代码链中,避免读写依赖显得尤其重要。如果读写依赖发生在操作数组时,许多编译器不能自动优化代码以避免读写依赖。所以推荐程序员手动去消除读写依赖,举例来说,引进一个可以保存在寄存器中的临时变量。这样可以有很大的性能提升。下面一段代码是一个例子:
不好的代码:
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
{
x[k] = z[k] * (y[k] - x[k-1]) ;
}
推荐的代码:
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 一次,然后将结果代入第二个式子,节约了一次除法运算。
8、 函数优化
(1)Inline函数
在 C++ 中,关键字 Inline 可以被加入到任何函数的声明中。 这个关键字请求编译器用函数内部的代码替换所有对于指出的函数的调用。 这样做在两个方面快于函数调用:第一,省去了调用指令需要的执行时间;第二,省去了传递变元和传递过程需要的时间。但是使用这种方法在优化程序速度的同时,程序长度变大了,因此需要更多的 ROM 。 使用这种优化在 Inline 函数频繁调用并且只包含几行代码的时候是最有效的。
( 2)不定义不使用的返回值
函数定义并不知道函数返回值是否被使用,假如返回值从来不会被用到,应该使用 void 来明确声明函数不返回任何值。
( 3)减少函数调用参数
使用 全局变量 比函数传递参数更加有效率。这样做去除了函数调用参数入栈和函数完成后参数出栈所需要的时间。然而决定使用全局变量会影响程序的模块化和重入,故要慎重使用。
( 4)所有函数都应该有原型定义
一般来说,所有函数都应该有原型定义。原型定义可以传达给编译器更多的可能用于优化的信息。
( 5)尽可能使用常量(const)
尽可能使用常量 (const) 。 C++ 标准规定,如果一个 const 声明的对象的地址不被获取,允许编译器不对它分配储存空间。这样可以使代码更有效率,而且可以生成更好的代码。
( 6)把本地函数声明为静态的(static)
如果一个函数只在实现它的文件中被使用,把它声明为静态的 (static)以强制使用内部连接。否则,默认的情况下会把函数定义为外部连接。这样可能会影响某些编译器的优化——比如,自动内联。