设为首页 加入收藏

TOP

编写高效的C程序与C代码优化(一)
2016-05-01 02:25:09 】 浏览:1420
Tags:编写 高效 程序 代码 优化

  但文章中的代码格式没有排版,不方便查看,而且有部分翻译错误以及其他错误,这篇文章除了参考原文和译文,也加入了自己的一些理解和代码,虽然是一篇2006年的文章,但是其中的一些技巧还是挺值得学习的,特重新整理出来与大家分享。

虽然对于优化C代码有很多有效的指导方针,但是对于彻底地了解编译器和你工作的机器依然无法取代,通常,加快程序的速度也会加大代码量。这些增加的代码也会影响一个程序的复杂度和可读性,这是不可接受的,比如你在一些小型的设备上编程,例如:移动设备、PDA……,这些有着严格的内存限制,于是,在优化的座右铭是:写代码在内存和速度都应该优化。

整型数 / Integers

在我们知道使用的数不可能是负数的时候,应该使用unsigned int取代int,一些处理器处理整数算数运算的时候unsigned int比int快,于是,在一个紧致的循环里面定义一个整型变量,最好这样写代码:

register unsigned int variable_name;

然而,我们不能保证编译器会注意到那个register关键字,也有可能,对某种处理器来说,有没有unsigned是一样的。这两个关键字并不是可以在所有的编译器中应用。记住,整形数运算要比浮点数运算快得多,因为处理器可以直接进行整型数运算,浮点数运算需要依赖于外部的浮点数处理器或者浮点数数学库。我们处理小数的时候要精确点些(比如我们在做一个简单的统计程序时),要限制结果不能超过100,要尽可能晚的把它转化成浮点数。

除法和余数 / Division and Remainder

在标准的处理器中,根据分子和分母的不同,一个32位的除法需要20-140个时钟周期来执行完成,等于一个固定的时间加上每个位被除的时间。

Time (分子/ 分母) = C0 + C1* log2(分子/分母)

= C0 + C1 * (log2(分子) - log2(分母)).
现在的ARM处理器需要消耗20+4.3N个时钟周期,这是一个非常费时的操作,要尽可能的避免。在有些情况下,除法表达式可以用乘法表达是来重写。比方说,(a/b)>c可以写成a>(c*b),条件是我们已经知道b为非负数而且b*c不会超过整型数的取值范围。如果我们能够确定其中的一个操作数为unsigned,那么使用无符号除法将会更好,因为它要比有符号除法快得多。

合并除法运算和取余运算 / Combining division and remainder

 在一些情况下,除法运算和取余运算都需要用到,在这种情况下,编译器会将除法运算和取余运算合并,因为除法运算总是同时返回商和余数。如果两个运算都要用到,我们可以将他们写到一起

int func_div_and_mod (int a, int b) {       return (a / b) + (a % b); }

除数是2的幂的除法和取余 / Division and remainder by powers of two

  如果除法运算中的除数是2的幂,我们对这个除法运算还可以进一步优化,编译器会使用移位运算来进行这种除法运算。所以,我们要尽可能调整比例为2的幂(比方说要用64而不用66)。如果是无符号数,它要比有符号的除法快得多。

typedef unsigned int uint; uint div32u (uint a) {      return a / 32; } int div32s (int a) {      return a / 32; }

这两种除法都会避免调用除法函数,另外,无符号的除法要比有符号的除法使用更少的指令。有符号的除法要耗费更多的时间,因为这种除法是使最终结果趋向于零的,而移位则是趋向于负无穷。

取模运算的替换 / An alternative for modulo arithmetic

我们一般使用取余运算进行取模,不过,有时候使用 if 语句来重写也是可行的。考虑下面的两个例子:

uint modulo_func1 (uint count) {     return (++count % 60); } uint modulo_func2 (uint count) {     if (++count >= 60)         count = 0;     return (count); }

第二个例子要比第一个更可取,因为由它产生的代码会更快,注意:这只是在count取值范围在0 – 59之间的时候才行。

但是我们可以使用如下的代码(笔者补充)实现等价的功能:

uint modulo_func3 (uint count) {     if (++count >= 60)         count %= 60;     return (count); }

使用数组索引 / Using array indices

假设你要依据某个变量的值,设置另一个变量的取值为特定的字符,你可能会这样做:

switch(queue) {     case 0 :   letter = 'W';         break;     case 1 :   letter = 'S';         break;     case 2 :   letter = 'U';         break; }

或者这样:

if(queue == 0)     letter = 'W'; else if ( queue == 1 )     letter = 'S'; else     letter = 'U';

有一个简洁且快速的方式是简单的将变量的取值做成一个字符串索引,例如:

static char *classes = "WSU"; letter = classes[queue];

全局变量 / Global variables

全局变量不会被分配在寄存器上,修改全局变量需要通过指针或者调用函数的方式间接进行。所以编译器不会将全局变量存储在寄存器中,那样会带来额外的、不必要的负担和存储空间。所以在比较关键的循环中,我们要不使用全局变量。
如果一个函数要频繁的使用全局变量,我们可以使用局部变量,作为全局变量的拷贝,这样就可以使用寄存器了。条件是本函数调用的任何子函数不使用这些全局变量。
举个例子:

int f(void); int g(void); int errs; void test1(void) {     errs += f();     errs += g(); } void test2(void) {     int localerrs = errs;     localerrs += f();     localerrs += g();     errs = localerrs; }

可以看到test1()中每次加法都需要读取和存储全局变量errs,而在test2()中,localerrs分配在寄存器上,只需要一条指令。

使用别名 / Using Aliases

考虑下面的例子:

void func1( int *data ) {     int i;     for(i = 0; i < 10; i++)         anyfunc(*data, i); }

即使*data从来没有变化,编译器却不知道anyfunc()没有修改它,于是程序每次用到它的时候,都要把它从内存中读出来,可能它只是某些变量的别名,这些变量在程序的其他部分被修改。如果能够确定它不会被改变,我们可以这样写:

void func1( int *data ) {     int i;     int localdata;     localdata = *data;     for(i=0; i<10; i++)         anyfunc(localdata, i); }

这样会给编译器优化工作更多的选择余地。

活跃变量和泄漏 / Live variables and spilling

寄存器的数量在每个处理器当中都是固定的,所以在程序的某个特定的位置,可以保存在寄存器中的变量的数量是有限制的。有些编译器支持“生命周期分割”(live-range splitting),也就是说在函数的不同部分,变量可以被分配到不同的寄存器或者内存中。变量的生存范围

首页 上一页 1 2 3 4 5 下一页 尾页 1/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇一起talk C栗子吧( 第一百四十回.. 下一篇指针和数组的掌握以及内存的管理

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目