C代码优化
C 代码优化方案
(零)
优化分类 :
(一)
选择合适的算法和数据结构 :
(二)
数据类型的选择 :
(三)
减少运算的强度 :
(1)
查表 (游戏程序员必修):
(2)
求余运算 :
(3)
平方运算 :
(4)
用移位实现 *,/ 运算 :
(5)
尽量变除法为乘法 :
(6)
使用自增,自减 :
(7)
提取公共的子表达式 :
(四)
结构体成员的布局 :
(1)
按照基本数据类型长度从低到高排列 :
(2)
结构体是最宽基本数据类型的整数倍 :
(3)
局部变量按照基本数据类型长度排列 :
(4)
避免在函数中频繁访问指针型参数所指向的值 :
(五)
循环优化:
(1)
提取公共部分 :
(2)
延时函数 :
(3)
循环部分展开 :
(4)
计算器自减的循环 :
(5)
不变量放到循环外 :
(6)
好的无限循环 :
(六)
条件优化 :
(1)
根据频率来排列switch中的case :
(2)
大的switch语句改为嵌套的switch :
(3)
组合条件 改为 嵌套的 if-else的使用 :
(七)
提高并行性 :
(1)
(采用流水,避免读取依赖) :
(八)
函数优化 :
(1)
内联函数 :
(2)
本地函数声明为static :
------------------------------------------------
---------------------详细内容--------------------
(零)优化分类 :
代码优化 : 时间优化, 空间优化
优化与程序可读性: 矛盾
需要优先优化的代码对象 : 频繁使用的代码。
所以在时间优化以及空间优化之间找到一种平衡。
而不要走极端。
(一)
选择合适的算法和数据结构 :
如:在一堆随机存放的数中,有大量的插入,删除操作,则应该使用链表,而不是数组。
用指针代替数组的索引,可以更有效率。
如: 数组索引 指针运算
For(;;){ p=array
A=array[t++]; for(;;){
a=*(p++);
。。。。。。。。。 。。。。。。
} }
对指针直接加减,比对数组索引操作,然后查找元素更加有效。
如 : 用 二分查找代替顺序查找 ,
用快速排序或者归并排序 代替 插入排序,冒泡排序,选择排序。
(二)
数据类型的选择 :
(1)空间优化 :
能够使用字符型(char)定义的变量,就不要使用整型(int)变量来定义;能够使用整型变量定义的变量就不要用长整型(long int)
(2)时间优化 :
由于char 占用一个字节,而int 通常是4个字节,即一个字。
所有,通常即使可以用char 也用int,可以 加快读取。而不是
读取一个字上的一个字节。
(三)
减少运算的强度 :
(1)
查表 (游戏程序员必修):
(先生成表,然后在查表。)
不要在主循环中做运算工作,一般是先运算好了,然后在循环中查表。
如:旧代码:
long factorial(int i)
{
if (i == 0)
return 1;
else
return i * factorial(i - 1);
}
新代码:
static long factorial_table[] =
{1, 1, 2, 6, 24, 120, 720 /* etc */ };
long factorial(int i)
{
return factorial_table[i];
}
如果表很大,不好写,就写一个init函数,在循环外临时生成表格。
(2)
求余运算 :
( 求余运算给按位与,或者移位运算 )
如: a=a%8;
可以改为:
a=a&7; 或者 a=a-(a>>3)<<3;
一般 a%(2^n)
改为 a&(2^n-1) 或者 a-(a>>n)<<n;
(3)
平方运算 :
a=pow(a, 2.0);
可以改为:
a=a*a;
同理:
如果是求3次方,如:
a=pow(a,3.0);
更改为:
a=a*a*a;
(4)
用 移位运算 实现 *,/ 运算 :
如: a*(2^n) a/(2^n)
分别改为左移以及右移运算。
如: a=a*9
可以改为:
a=(a<<3)+a
z = y * 33;
改为z = (y << 5) + y;
尽量 将乘法改为加法,尽量将除法改为乘法。
(5)
尽量变除法为乘法 :
不好的代码:
int i, j, k, m;
m = i / j / k;
推荐的代码:
int i, j, k, m;
m = i / (j * k);
(6)
使用自增,自减 :
x=x+1; 改为 x++;或者++x;
(7)
提取公共的子表达式 :
不好的代码:
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;
(四)
结构体成员的布局 :
1.结构体的大小是最宽基本数据成员大小的整数倍。
2.数据对齐方式:前面已经摆好的数据成员占有的空间
是当前数据成员大小的 整数倍。
(1)
按照基本数据类型长度从高到低排列 :
不好的代码,普通顺序:
struct
{
char a[5];
long k;
double x;
} baz;
推荐的代码,新的顺序并手动填充了几个字节:
struct
{
double x;
long k;
char a[5];
char pad[7];
} baz;
填了几个字节,是为了是结构体是最宽基本数据成员的整数倍。
(2)
结构体是最宽基本数据类型的整数倍 :
(3)
局部变量按照基本数据类型长度排列 :
把长的变量放在短的变量前面。
不好的代码,普通顺序
short ga, gu, gi;
long foo, bar;
double x, y, z[3];
char a, b;
float baz;
推荐的代码,改进的顺序
double z[3];
double x, y;
long foo, bar;
float baz;
short ga, gu, gi;
(4)
避免在函数中频繁使用指针型参数指向的值 :
可以用一个局部变量存储指针型参数所指向的值,然后对该局部变量操作,最后再把该局部变量赋值给指针型参数所指向的值。
(五)
循环优化:
(1)
将不变量或者五循环变量参与的部分提取到循环外 :
(2)
延时函数 :
通常使用的延时函数均采用自加的形式:
void delay (void)
{
unsigned int i;
for (i=0;i<1000;i++) ;
}
将其改为自减延时函数:
void delay (void)
{
unsigned int i;
for (i=1000;i>0;i--) ;
}
在使用while循环时也一样,使用自减指令控制循环会比使用自加指令控制循环生成的代码更少。
(3)
循环部分展开 :
旧代码:
for (i = 0; i < 100; i++)
{
do_stuff(i);
}
新代码:
for (i = 0; i < 100; )
{
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
}
如:文件读写,一次读取一个字节,可以更改为一次读取
4个字节,到最后不足4个的时候再一次读取一个字节。
(4)
计算器自减的循环 :
旧代码:
for (i = 1; i <= MAX; i++)
{
。。。
}
新代码:
i = MAX+1;
while (--i)
{
。。。
}
(5)
不变量放到循环外 :
不好的代码(在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()的值,这样可以避免重复计算。
(6)
好的无限循环 :
for (;;) 比while (1)好。
(六)
条件优化 :
(1)
根据频率来排列switch中的case :
对case的值依照发生的可能性进行排序,
把最有可能的放在第一位。
如:不好的代码:
int days_in_month, short_months, normal_months, long_months;
。。。。。。
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;
}
推荐的代码:
int days_in_month, short_months, normal_months, long_months;
。。。。。。
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;
}
(2)
大的switch语句改为嵌套的switch :
switch嵌套如下 :
switch(表达式)
{
case 标识1: break;
case 标识2: break;
...
default : switch(表达式){
case 标识3: break ;
case 标识4: break ;
}
}
(两个switch中的表达式是相同的,
所以把大 的switch改为嵌套的switch )
嵌套部分用来处理,不经常发生的情况。
(3)
组合条件 改为 嵌套的 if-else的使用 :
(4)
避免使用 多个if-else的公用代码块 :
一些公用处理模块,为了满足各种不同的调用需要,往往在内部采用了大量的if-then-else结构,这样很不好,判断语句如果太复杂,会消耗大量的时间的,应该尽量减少公用代码块的使用
(七)
提高并行性 :
(1)
(采用流水,避免读取依赖) :
尽可能把长的有依赖的代码链分解成几个可以在流水线执行单元中并行执行的没有依赖的代码链。
不好的代码:
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;
int i;
sum1 = sum2 = sum3 = sum4 = 0.0;
for (i = 0; i < 100; i += 4)
{
sum1 += a[i];
sum2 += a[i+1];
sum3 += a[i+2];
sum4 += a[i+3];
}
sum = (sum4+sum3)+(sum1+sum2);
通常做法是:
由于一次取4个,到最后有可能不足4个,发生越界。
正确为:
i=0;
for(j=0;j<n/4;j++)
{
// j 仅仅是计算器,i才是索引。
// j的循环次数为n/4-1-0+1=n/4;
sum1+=a[i];
sum2+=a[i+1];
sum3+=a[i+2];
sum4+=a[i+3];
i+=4;
}
sum = (sum4+sum3)+(sum1+sum2);
for(;i<n;i++)
{
sum+=a[i];
}
使用4 路分解是因为这样使用了4段流水线浮点加法,浮点加法的每一个段占用一个时钟周期,保证了最大的资源利用率。
(八)
函数优化 :
(1)
内联函数 :
(2)
本地函数声明为static :