第五部分:代码改善
25 代码调整策略
70年代,程序员意识到过分专注性能会损失程序的可读性和可维护性。
对普通用户来说,程序员按时交付软件、一个清爽的界面、避免死机,常常比性能更重要。
优化效率前先考虑几个问题:
1、程序需求(客户的需求要求太高,其实根本没有必要这么高)
2、程序的设计(当前的设计很难做良好的优化工作)
3、具体设计(比如采用快速排序还是冒泡排序)
4、同操作系统的交互(影响你的效率,有时候你自己也没有意思到有系统调用,比如编译器会生成系统调用,或者某个库使用了)
5、代码编译(好的编译器都会替你优化)
6、硬件
7、代码调整(本章主题,细节代码)
“不足4%的部分常常占用了超过50%的运行时间”
1、“代码行数多,并不代表运行速度慢”
for(int i=0;i<10;i++)
a[i]=i;
与
a[0]=1;a[2]=2;……a[9]=9;
相比,明显是后面一个更快。
2、“优化要考虑可移植性,可能一种方法放到另外一种语言,甚至同一语言的另外一种编译器,都可能产生反作用。”
3、“很难找到那4%的部分,程序员做的优化往往带来的效果都微不足道。”
4、“对那4%的优化,可能顾此失彼,反而导致整体性能的下降。”
5、“速度的优化会带来可读性、封装性、正确性的破坏,带来的好处并不值得。”
6、“运行速度的要求现在越来越低与正确性等方面。”
7、“程序员应该使用高质量的设计,把代码写正确,使之模块化并易于修改,将让后期的维护工作变得很容易。”
8、“编译器优化打开会×××能”
c++编译器打开优化基本会提高大约50%的性能。可以参考这个例子:
http://topic.csdn.net/u/20091113/14/9d793a37-9953-43be-940f-26e4b64d8a6b.html
优化的很激进。
9、“系统影响”
(1)访问内存比访问网络和磁盘快的百倍。
(2)系统调用
(3)内存数据分配不当,导致内存分页也会影响性能:
for(int column=0;column<100;column++)
for(int row=0;row<1000000;row++)
table[row][column] = new XXX;
因为一般读取数据,都是读一行,然后取每一列,这种分配方案会导致在读取时都会切换内存页(缺页中断),影响性能。
所以应该是:
for(int row=0;row<1000000;row++)
for(int column=0;column<100;column++)
table[row][column] = new XXX;
不过这样会导致局部运行变慢,具体见下章。
10、c++函数的参数越多,调用速度越慢。【应该都是说基本数据类型,不知道结构体怎么样】
很神奇java的函数参数数量不影响运行速度,应该都是做了优化的。
多态函数调用速度最慢。
11、没必要把数组的移动变成多维指针来操作,因为编译器基本都替我们优化了,如果我们再做,会导致可读性下降。
26 代码调整技术
“减少占用资源主要是通过调整类和数据结构,而非代码调整。”
1、case语句、if=else先判断常见的。
2、用查表法代替复杂逻辑。
3、算出的结构可以放在缓存中下次用。
4、把循环中的判断外提
for(int i=0;i<count;i++)
{
if(type=A)
xxxx
else
xxx
}
变成
if(type=A)
for(int i=0;i<count;i++)
xxx
else
for(int i=0;i<count;i++)
xxx
但是这样就出现了重复代码,对维护代码是个问题。
5、展开
for(int i=0;i<count-2;i++)
a[i]=i;
变成
for(int i=0;i<count;i++)
{
a[i]=i;
a[i]=++i;
a[i]=++i;
}
xxxx//处理额外数据
基本可以当成是不良编程,不过如果是很简单的处理,可以提供34%的效率。
【感觉意义不大,如果是循环内部处理很耗时的话,这种提高就被冲淡了】
6、减少循环内部的处理
for(int i=0;i<count;i++)
a[i]=b[i]*p->p->p;
变成
pr=p->p->p;
for(int i=0;i<count;i++)
a[i]=b[i]*pr;
7、哨兵值【这个感觉比较变态,第一次见这么诡异的方法】
for(int i=0;i<count;i++)
if(item[i]==type)
{bFind=true;break;}
改成
item[count]=type;
while(item[i]!=type)
i++;
if(i<count)
xxxx
太诡异了,等于少了一个判断条件
8、把最忙的循环放在内层
观察上章9,如果不考虑分配内存,那么第一个的循环总算是1000000*101次,而第二个是1000001*100,少了999900次循环。
但是虽然局部优化了,整体下降了。
如果不是分配内存而是简单处理,倒是不错的。
9、用加法替代乘法
10、循环下标不用浮点型而用整型【效率提高3.5倍,不过正常人应该都不会用浮点型做循环循环下标吧】
11、用一维数组代替多维数组,没有什么效率提高,编译器已经优化了。
12、把常用计算结果变成常量。
比如int log2(int x){return log(x)/log(2);}
把log(2)变成常量使用。
13、小心系统函数
对于不需要精度的函数,直接自己写。
14、使用正确的常量类型
当赋值变量时,给于的值需要转换,那么会消耗时间,因为要做强制转换。【有空编码试试】
15、预先算出结果,直接查表
16、把子程序变成内联函数
17、用低级语言重写代码【改成汇编难度挺大的,尤其是比较复杂的程序,用汇编重写太难了- - 估计写几个算法还可以,不过不清楚怎么调用汇编的程序,难道当成是一个独立程序调用返回结果吗?还是编译器可以把汇编出来的东西当做程序一部分调用?tc有个small模式可以调用汇编代码.ASM文件】
有空试试这样编码
http://zhidao.baidu.com/question/31397560.html
试了下linux版的卡死不动了,什么都没打印出来。
linux上面的汇编蛮难理解的,跟以前上学学的都不一样。
g++ -S可以产生汇编代码。
拼写上多了一些字符。一些指令后面多了个L,源操作数与目的操作数位置对调了,常数前面加了$,寄存器前面加了%
http://tieba.baidu.com/f?kz=310375556
http://wenku.baidu.com/view/e8752e7f5acfa1c7aa00cc39.html
有空可以看看。
也了一个简单的例子:
int a = 1;
int b = 2;
int main()
{
__asm__
(
"movl a,%ecx \n"
"xchg b,%ecx \n"
"movl %ecx,a \n"
);
return 0;
}
变量必须用全部定义,用static都不行。
不过一般现在在代码里面内嵌汇编意义不大,看这篇
http://topic.csdn.net/u/20100413/20/87aa7378-cd9f-44ad-90ae-4201a3680b71.html