C语言代码优化方案
积时会溢出,所以只能在一定范围的除法中使用。
不好的代码: int i, j, k, m; m = i / j / k; 推荐的代码: int i, j, k, m; m = i / (j * k);
(6) 使用增量和减量操作符
在使用到加一和减一操作时尽量使用增量和减量操作符,因为增量符语句比
赋值语句更快,原因在于对大多数 CPU 来说,对内存字的增、减量操作不必明
可以用指针运算代替数组索引,这样做常常能产生又快又短的代码。与数组索引
相比,指针一般能使代码速度更快,占用空间更少。使用多维数组时差异更明显。
下面的代码作用是相同的,但是效率不一样。
数组索引
指针运算
for(;;) {
a=array[t++]; …… }
p=a rray; for(;;) {
a=*(p++); …… }
qq = (qq + rr) >> 1; } } rr = a - qq * qq; *q = qq; *r = rr; }
5. 循环优化
(1) 充分分解小的循环
要充分利用 CPU 的指令缓存,就要充分分解小的循环。特别是当循环体本
身很小的时候,分解循环可以提高性能。注意:很多编译器并不能自动分解循环。
不好的代码: // 3D 转化:把矢量 V 和 4x4 矩阵 M 相乘 for (i = 0; i < 4; i++) {
r[ i] = 0; for (j = 0; j < 4; j++) {
r[ i] += M[j][ i]*V[j]; } }
推荐的代码: r[0] = M[0][0]*V[0] + M[1][0]*V[1] + M[2][0]*V[2] + M[3][0]*V[3]; r[1] = M[0][1]*V[0] + M[1][1]*V[1] + M[2][1]*V[2] + M[3][1]*V[3]; r[2] = M[0][2]*V[0] + M[1][2]*V[1] + M[2][2]*V[2] + M[3][2]*V[3]; r[3] = M[0][3]*V[0] + M[1][3]*V[1] + M[2][3]*V[2] + M[3][3]*V[3];
在 ICCAVR 中,可以在 Options 中设定使用 printf 参数,尽量使用基本型参数 (%c 、 %d 、 %x 、 %X 、 %u 和 %s 格 式 说 明 符 ) , 少 用 长 整 型 参 数
(%ld 、 %lu 、 %lx 和 %lX 格式说明符 ) ,至于浮点型的参数 (%f) 则尽量不 要使用,其它 C 编译器也一样。在其它条件不变的情况下,使用 %f 参数,会 使生成的代码的数量增加很多,执行速度降低。
} 新代码: static long factorial_table[] = {1,1 ,2 ,6 ,24 ,120 ,720 /* etc */ }; long factorial(int i) {
return factorial_table[ i]; } 如果表很大,不好写,就写一个 init 函数,在循环外临时生成表格。
共的子表达式(在 里有一项“全局优化”选项可以完成此工作,但效果
就不得而知了)。
不好的代码:
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; 不好的代码:
(4) 用移位实现乘除法运算
a=a*4; b=b/4; 可以改为: a=a<<2; b=b>>2; 通常如果需要乘以或除以 2n,都可以用移位的方法代替。在 ICCAVR 中, 如果乘以 2n ,都可以生成左移的代码,而乘以其它的整数或除以任何数,均调 用乘除法子程序。用移位的方法得到代码比调用乘除法子程序生成的代码效率高。 实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果,如: a=a*9 可以改为: a=(a<<3)+a 采用运算量更小的表达式替换原来的表达式,下面是一个经典例子: 旧代码: x = w % 8;
这个规则同样适用于类的成员的布局。
(3) 按数据类型的长度排序本地变量
当编译器在给本地变量分配空间时,它们的顺序和它们在源代码中声明的顺
序一样,和上一条规则一样,应该把长的变量放在短的变量前面。如果第一个变
量对齐了,其它变量就会连续的存放,而且不用填充字节自然就会对齐。有些编
译器在分配变量时不会自动改变变量顺序,有些编译器不能产生 4 字节对齐的 栈,所以 4 字节可能不对齐。下面这个例子演示了本地变量声明的重新排序:
} *r = a - *q * *q; } 推荐的代码:
void isqrt(unsigned long a, unsigned long* q, unsigned long* r) {
unsigned long qq, rr; qq = a; if (a > 0) {
while (qq > (rr = a / qq)) {
内存的偶数边界。
(2) 把结构体填充成最长类型长度的整倍数
把结构体填充成最长类型长度的整倍数。照这样,如果结构体的第一个成员 对齐了,所有整个结构体自然也就对齐了。下面的例子演示了如何对结构体成员 进行重新排序:
普通顺序: struct {
char a[5]; long k; double x; } baz; 新的顺序并手动填充了 7 个字节,使结构体大小变成 24 字节,3 倍于 double 的最大 8 字节尺寸: struct { double x; long k; char a[5] ; char pad[7] ; } baz;
(4) 把频繁使用的指针型参数拷贝到本地变量
避免在函数中频繁使用指针型参数指向的值。因为编译器不知道指针之间是
否存在冲突,所以指针型参数往往不能被编译器优化。这样数据不能被存放在寄
存器中,而且明显地占用了内存带宽。注意,很多编译器有“假设不冲突”优化
开关(在 VC 里必须手动添加编译器命令行 /Oa 或 /Ow ),这允许编译器假设 两个不同的指针总是有不同的内容,这样就不用把指针型参数保存到本地变量。
否则,请在函数一开始把指针指向的数据保存到本地变量。如果Leabharlann 要的话,在函数结束前拷贝回去。
不好的代码: // 假设 q != r void isqrt(unsigned long a, unsigned long* q, unsigned long* r) {
*q = a; if (a > 0)
{ while (*q > (*r = a / *q)) { *q = (*q + *r) >> 1; }
短了。
(7) 使用复合赋值表达式
复合赋值表达式 ( 如 a-=1 及 a+=1 等 ) 都能够生成高质量的程序代码。
(8) 提取公共的子表达式
在某些情况下,C++ 编译器不能从浮点表达式中提出公共的子表达式,因为
这意味着相当于对表达式重新排序。需要特别指出的是,编译器在提取公共子表
达式前不能按照代数的等价关系重新安排表达式。这时,程序员要手动地提出公
(2) 求余运算
a=a%8; 可以改为: a=a&7; 说明:位操作只需一个指令周期即可完成,而大部分的 C 编译器的“%” 运算均是调用子程序来完成,代码长、执行速度慢。通常,只要求是求 2n 方的 余数,均可使用位操作的方法来代替。
(3) 平方运算
a=pow(a, 2.0); 可以改为: a=a*a; 说明:在有内置硬件乘法器的单片机中(如 51 系列),乘法运算比求平方运 算快得多,因为浮点数的求平方是通过调用子程序来实现的,在自带硬件乘法器 的 AVR 单片机中,如 ATMega163 中,乘法运算只需 2 个时钟周期就可以完成。 既使是在没有内置硬件乘法器的 AVR 单片机中,乘法运算的子程序比平方运算 的子程序代码短,执行速度快。 如果是求 3 次方,如: a=pow(a, 3.0); 更改为: a=a*a*a; 则效率的改善更明显。
不好的代码,普通顺序: 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;
最少代价的情况下实现最好的结构体和结构体成员对齐,建议采取下列方法:
(1) 按数据类型的长度排序
把结构体的成员按照它们的类型长度排序,声明成员时把长的类型放在短的 前面。编译器要求把长型数据类型存放在偶数地址边界。在申明一个复杂的数据 类型(既有多字节数据又有单字节数据)时,应该首先存放多字节数据,然后再 存放单字节数据,这样可以避免内存的空洞。编译器自动地把结构的实例对齐在
h += 14; printf("%d", h); }
/* 位操作比求余运算快 */ /* 乘法比平方运算快 */ /* 位移乘法比乘法快 */
/* 加法比乘法快 */
(5) 避免不必要的整数除法
整数除法是整数运算中最慢的,所以应该尽可能避免。一种可能减少整数除
法的地方是连除,这里除法可以由乘法代替。这个替换的副作用是有可能在算乘
显地使用取内存和写内存的指令,比如下面这条语句: