ways to speed up your program

怎样提高程序的效率? 

通常情况下,实际的计算时间都被外围任务(如I/O或内存的分配)占用了,可以通过使用缓冲区和超高速缓存来提高速度。一些建议如下:

l  对常用的变量使用register声明,如果可行,在内部块中使用;

l  仔细检查算法,尽可能地利用对称压缩明确条件的个数;

l  检查控制流:确保一般情况先检查并且处理得更简单,如果&&||的某一侧通常决定表达式的结果,那么就把它放在左边;

l  可能的情况先使用memcpy代替memmove

l  使用机器相关和厂商特定的例程及#pragma

l  手工将公共子表达式置入临时变量(好的编译器会为你代劳)

l  将关键的内循环代码移出函数,置入宏或内联(inline,如果不变,移出循环,如果循环的终止条件很复杂但并不随循环变化,可以先计算,将结果置入临时变量中;

l  如果可能,将递归改为迭代

l  打开小循环;

l  比较whilefor还是do/while循环在你的编译器下生成的最好代码,检查增加还是减少循环控制变量运行得最好;

l  去掉goto语句,因为有的编译器在有goto存在的条件下不能优化的很好;

l  用指针而不是数组下标检索数组;

l  降低精度,如果可以用float代替double可以在ANSI编译器下导致更快的单精度算术;

l  缓存或预先计算常用值表;

l  优先使用标准库函数而不是你自己的版本;

l  最后一招:用汇编手工编写关键代码(或者对编译器的汇编输出进行手工微调),如果可能,使用asm指令。

数据

数据

       变量的三个属性:作用域、链接属性和存储类型,决定了该变量的可视性(也就是它可以在什么地方使用)和生命期(它的值将保持多久)。

基本数据类型

       C语言中,仅有4种基本数据类型:整型、浮点型、指针和聚合类型(如数组和结构等)。所有其他的类型都是从这4中基本类型的某种组合派生而来的。

整型家族

       听上去长整型要比短整型所能表示的值要大,但是这个假设并不一定正确。规定整型值相互之间大小的规则很简单:

       长整型至少应该和整型一样长,而整型至少应该和短整型一样长。

       而对于int是选择多少位的缺省值是由编译器的设计者决定的,一般都会选择最高效的位数。

       缺省的char要么是signed char,要么是unsigned char,这取决于编译器,这个事实意味着不同机器上的char可能拥有不同范围的值。所以只有当程序所使用的char型变量的值位于signed charunsigned char的交集中,这个程序才是可移植的。例如,ASCII字符集中的字符都是位于这个范围之内的。

       如果一个多字节字符常量的前面有一个L,那么它就是款字符常量(wide character literal),比如:L’X’

       枚举enumerated常量类型就是指它的值为符号常量而不是字面值的类型,它以下面的形式声明:

       enum month{Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec}; 

浮点类型

浮点数字面值在缺省情况下是double类型的。

指针

       指针是C语言为什么这么流行的一个重要原因。指针可以有效地实现诸如treelist这类高级数据结构。用C语言可以比使用其他语言编写出更为紧凑和有效的程序。但同时,C对指针使用的不加限制正是许多令人欲哭无泪和咬牙切齿的错误的根源。

基本声明

       用于修改变量的长度或是否为有符号的关键字为:

short long signed unsigned

       如果声明中已经至少有了一个其他的说明符,关键字int就可以省略。

声明简单数组

       C数组一个值得关注的地方是,编译器并不检查程序对数组下标的引用是否在数组的合法范围之内,这种不加检查的行为有好处也有坏处,好处是不需要浪费时间对有些已知是正确的数组下边进行检查,坏处是这样做将使无效的下标引用无法被检测出来

隐式声明

       函数如果不显示地声明返回值的类型,它默认返回整型。

Typedef

       typedef允许为各种数据类型定义新名字。在原来的声明前面加上typedef就可以为数据类型定义新名字。比如typedef char * ptr_to_char使用typedef声明类型可以减少使声明变得又臭又长的危险,尤其是那些复杂的声明。你应该使用typedef而不是#define来创建新的类型名,因为后者无法正确地处理指针类型

常量const

int *pi;一个普通的指向整型的指针;

int const *pci;一个指向整型常量的指针;

int *const cpi;一个指向整型的常量指针。

作用域

       编译器可以确认4中不同类型的作用域:文件作用域、函数作用域、代码块作用域和原型作用域。

代码块作用域

       声明于每个代码块的变量无法被另一个代码块访问,因为它们的作用域并无重叠之处,由于两个代码块的变量不可能同时存在,所以编译器可以把它们存储于同一个内存地址。

链接属性

       链接属性一共有3种:external外部、internal内部和none无。

       没有链接属性的标识符none总是被当做单独的个体,也就是说该标识符的多个声明被当做独立不同的实体。属于internal链接属性的标识符在同一个源文件内的所有声明中都指向同一个实体,但位于不同源文件的多个声明则分属不同的实体。最后,属于external链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体。

       在一个变量声明前加上一个static就可以将它的链接属性变为internal

       如果你在一个地方定义变量,并在使用这个变量的其他源文件的声明中添加external关键字,会更容易理解。

存储类型

       变量的存储类型storage class是指存储变量值的内存类型。变量的存储类型决定变量何时创建、何时销毁以及它的值将保持多久。有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。

       对于在代码块内部声明的变量,如果给它加上关键字static,可以使它的存储类型从自动变为静态。具有静态存储类型的变量在整个程序执行过程中一直存在,而不仅仅在声明它的代码块的执行时存在。注意函数的形式参数不能声明为静态,因为实参总是在堆栈中传递给函数,用于支持递归。

       而对于register变量,如果声明过多,编译器会只选取前几个实际存储在寄存器中,其余的就按照普通自动变量处理。

       在有些计算机中,如果把指针声明为寄存器变量,程序的效率将能得到提高,尤其是那些频繁执行间接访问操作的指针。

       寄存器变量的创建和销毁时间和自动变量相同,但需要一些额外的工作,即要保存寄存器的原始状态。在许多机器的硬件实现中,并不为寄存器指定地址,同样,由于寄存器值的保存和恢复,某个特定的寄存器在不同的时刻所保存的值不一定相同,基于这些理由,机器并不向你提供寄存器变量的地址

       自动变量如果不初始化,那么它们的值总是垃圾。

static关键字

       当位于不同的上下文环境时,static关键字具有不同的意思。

l  static用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性,从external改为internal,但标识符的存储类型和作用域不受影响,用这种方式声明的函数或变量只能在声明它们的源文件中访问;

l  static用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,从自动变量修改为静态变量,当变量的链接属性和作用域不受影响,用这种方式声明的变量在程序执行之前创建,并在程序的整个执行期间一直存在,而不是每次在代码块开始执行时创建,在代码块执行完毕后销毁。

l  所以,static不改变作用域,改变的是链接属性(代码块外或函数)或存储类型(代码块内)

总结

       具有external链接属性的实体在其他语言的术语里称为全局global实体,所有源文件的所有函数均可以访问。

     如果一个变量声明于代码块内部,在它前面添加extern关键字将使它所引用的是全局变量而非局部变量。

       具有external链接属性的实体总是具有静态存储类型。

       局部变量有函数内部使用,不能被其他函数通过名字引用,它在缺省情况下的存储类型为自动,这是基于两个原因:

1.        当这些变量需要时才为它们分配存储,这样可以减少内存的总需求量;

2.        在堆栈上为它们分配存储可以有效地实现递归。