C语言性能优化个人总结
基本建议
当编译器分配给本地变量空间时,它们的顺序和它们在源代码中声明的顺序一样,和上一条规则一样,应该把长的变量放在短的变量前面。如果第一个变量对齐了,其它变量就会连续的存放,而且不用填充字节自然就会对齐。
充分分解小的循环:要充分利用CPU的指令缓存,就要充分分解小的循环。特别是当循环体本身很小的时候,分解循环可以提高性能。注意:很多编译器并不能自动分解循环。
在for循环中每次循环都需要对i进行是否越界的判断,而while循环查找只要对比一次即可,避免了查找过程中每次比较后都要判断查找位置是否越界的小技巧,看似与原先差别不大,但是总数据较多时,效率提高很明显,是非常好的编程技巧。
整数除法是整数运算中最慢的,所以应该尽可能避免。一种可能减少整数除法的地方是连除,这里除法可以由乘法代替。这个替换的副作用是有可能在算乘积时会溢出,所以只能在一定范围的除法中使用。
在使用到加一和减一操作时尽量使用增量和减量操作符,因为增量符语句比赋值语句更快,原因在于对大多数CPU来说,对内存字的增、减量操作不必明显地使用取内存和写内存的指令.
显然,for (;;)指令少,不占用寄存器,而且没有判断、跳转,比while (1)好。
当数据保存到内存时存在读写依赖,即数据必须在正确写入后才能再次读取。虽然AMD Athlon等CPU有加速读写依赖延迟的硬件,允许在要保存的数据被写入内存前读取出来,但是,如果避免了读写依赖并把数据保存在内部寄存器中,速度会更快。在一段很长的又互相依赖的代码链中,避免读写依赖显得尤其重要。如果读写依赖发生在操作数组时,许多编译器不能自动优化代码以避免读写依赖。所以推荐程序员手动去消除读写依赖,举例来说,引进一个可以保存在寄存器中的临时变量。
一般来说,所有函数都应该有原型定义。原型定义可以传达给编译器更多的可能用于优化的信息。
尽可能使用常量(const)。C++ 标准规定,如果一个const声明的对象的地址不被获取,允许编译器不对它分配储存空间。这样可以使代码更有效率,而且可以生成更好的代码。
数学运算的计算时间:由于不同指令本身的速度就是不一样的,比较、整型的加减、位操作速度都是最快的,而除法/取余却很慢。下面有一个更详细的列表,为了更直观一些,用了clock cycle 来衡量,不过这里的 clock cycle 是个平均值,不同的 CPU 还是稍有差异
* comparisons (1 clock cycle)
* (u)int add, subtract, bitops, shift (1 clock cycle)
* floating point add, sub (3~6 clock cycle)
* indexed array access (cache effects)
* (u)int32 mul (3~4 clock cycle)
* Floating point mul (4~8 clock cycle)
* Float Point division, remainder (14~45 clock cycle)
* (u)int division, remainder (40~80 clock cycle)
在性能优化方面永远注意80-20准备,不要优化程序中开销不大的那80%,这是劳而无功的。
程序中对时间要求苛刻的部分可以用内嵌汇编来重写,以带来速度上的显著提高。但是,开发和测试汇编代码是一件辛苦的工作,它将花费更长的时间,因而要慎重选择要用汇编的部分。
通过位运算进行加速
(类似)x=x*10 | x=(x<<1)+(x<<3) |
---|---|
x/=2 | x>>=1 |
x%2==1 | x&1 |
x%2==0 | !(x&1) |
(long long)int a,b,c; c=a;a=b;b=c; |
(long long)int a,b; a =b;b =a;a^=b |
x*=2n | x<<=n |
x/=2n | x>>=n |
int a; if(a<0) a=-a; | int a; a=(a^(a>>31))-(a>>31); |
long long int a; if(a<0) a=-a; | long long int a; a=(a^(a>>63))-(a>>63); |
(取得int的最大值) | (1<<31)-1 |
(取得long long int的最大值) | (1<<63)-1 |
(取得int的最小值) | 1<<31 |
(取得long long int的最小值) | 1<<63 |
(long long)int a,b; (a+b)/2; | (long long)int a,b; ((a^b)>>1)+(a&b); |
参考资料