程序性能优化的常用技术
Version 1.0
2008-4-28
以下内容主要摘自《程序设计方法与优化》(西交大出版)。
l 优化分类:
Ø 算法级优化,
Ø 语音级优化,如减少需要执行的语句,指针移动代替内存copy,初始化操作放到开始等
Ø 指令级优化,常针对关键部分进行。
l 优化内容:
1, 代码替换:使用周期短的指令代替周期长的指令;
2, 分支预测,指令预读:Pentium以上CPU执行指令前会预读一些指令,有分支会造成预读失效;
3, 并行指令;
4, MMX指令,一次可处理8字节的数据;
l C、C++基本优化方法:
1、 自加、自减指令有利于编译器生成高效代码,a=a-n会比a-=n多出几条汇编指令;
2、 使用位移代替*/,如a=a*9 >> a=(a<<3)+a;
3、 减少运算强度,如用a&7代替a%8;
4、 提取公共子表达式:e=b*c/d; f=b/d*a >> t=b/d; e=c*t; f=a*t;
5、 避免整数除法——除法是最慢的;
6、 将不依赖循环的操作提到循环之外;
7、 内存换速度,如将某些复杂运算的结果存储起来,使用时直接查表;
8、 数据类型,32位快于16位或者8位;无符号型常用于除法余数、循环计数、数组下标,有符合型常用于浮点型的转换;
9、 数组类型较指针有利于编译器执行优化;
10、 使用for(;;)代替while(1),前者只生成一条汇编代码,而后者是好几条,且有寄存器操作;
11、 分解小循环以利用CPU指令缓存,for(i=0;i<4;i++){r[i]=m[i]*v[i]}的效率不如去掉循环直接写4条语句;
一个测试数据:VC6Debug版未分解版本耗时2.3秒,分解版本1.4秒;Release版未分解时耗时0.7秒,分解版本耗时0秒;
12、 避免读写依赖——数据必须正确写入后才能再次读取,如:
For(u_int k=1; k<10; k++){
X[k] = x[k-1]+y[k]; //x[k]依赖x[k-1],而后者在不断变化
}
更改为:
T=x[0];
For(u_int k=1; k<10; k++){
T += y[k];
X[k]=t;
}
VC6下的测试,Debug版两种方式性能基本无差别,大概都是4秒;Release版后者比前者快,如前者需1.7秒,后者只需0.7秒;
13、 多算式融合,如将a[i];i++ >> a[i++],前者可能需要读两次i而后者只需一次;
14、 优化switch:switch可能转化为跳转表或比较链/数(case使用小而连续的整数可保证它转化为跳转表);将最可能的值放到前面有利于比较链形式时的性能——对多if判断也应这样做;
15、 注意指针参数,编译器难以对指针参数进行优化,建议多使用本地变量而避免频繁使用指针变量;
16、 避免C++中临时对象的产生,构函中使用初始化列表而非传统赋值,避免内部对象的默认构造;
17、 使用static、inline;
18、 使用指针及引用代替结构赋值;
19、 I/O,提供文件I/O的方法——内存缓冲与内存映射文件;
一些编程风格建议:
l 使用初始化列表而不是在构函中赋值,并尽量使其顺序与成员定义的顺序一致,这可以避免某些编译器的告警;
l 基类中定义的virtual函数,子类中override时给函数也加上virtual关键字,这样代码读起来比较方便;
l 文件最后留几个空行,以避免某些编译器的告警;
l 注意文件名大小写;
l 编译宏定义写到文件中尽可能靠前的位置;