变量与寄存器
未指定优化选项时,编译器会将程序中的变量储存到内存上,程序在执行相关运算时会根据需要装载到寄存器,当运算结束后再将运算器上的内容移到内存,然后生成目标代码。
但如果指定了优化选项,变量就会尽可能地储存在寄存器上,从而减少访存时间。
===============
变量间的乘法运算
和加法运算相同,CPU内部有多个乘法器,它们是为了同时计算乘法运算而存在的。
变量与常量的乘法
当编译器进行常量乘法计算时,尽可能不使用CPU内的乘法计算器,而使用移位结合加法运算。
···················
当我们做“2的乘方+1”(即3,5,9等)的乘法运算时,采用CPU中具备的lea(load effective address)指令,也能降低执行成本。比如:
将变量与常量5相乘,相当于将寄存器内的值*4+寄存器的值。 注意编译器在进行计算时可能利用cpu内部所有的计算功能来计算,本来LEA是用于计算地址值的,但这里却用于简化乘法计算。
负数的乘法通过移位运算也可以代替乘法运算。
=================
除法操作是比乘法更为耗时的运算。
与乘法运算相同,除以2的乘方时,可以通过寄存器内数据的移位操作来提高效率。但是对于负数的除法运算还必须另外考虑????
除数不是2的乘方的除法计算
当除数不是2的乘方时,编译器也可能会进行优化。但是相关计算比较繁琐,对于有符号数的要转化为无符号数进行计算。
利用减法和移位求除数是5的除法运算。
A/5可以转化为先计算A=A>>2; 由于除4带来看“误差”miss=A
单核CPU中一些运算也可能是并行的,比如浮点数乘法运算。
尽管许多程序还是依赖单线程的执行,现代处理器在单核中也提供了不少的并行性。例如:单个CPU可以同时执行4个浮点数乘,等待4个内存请求并执行一个分支预判。
避免或减少使用本地变量。
本地变量通常都存储在栈上。不过如果数量比较少,它们可以存储在CPU寄存器中。在这种情况下,函数不但得到了更快访问存储在寄存器中的数据的好处,也避免了初始化一个栈
帧的开销。
编译器、中间件篇
操作系统和编译器的基础软件,数据库软件、网络协议栈,扩充文件系统这些软件叫做中间件。它们舍弃了复杂且原始的计算机操作,将程序员要解决的问题集中起来处理。
- 输入输出操作
输入输出操作十分耗时,如果可以将多次输入输出操作集中起来归并处理。
有效利用输入输出缓冲区非常重要。
- 字符串操作
将strcpy函数替换为memcpy函数能够提高效率。
-
算法
结合数据来调整算法。
4.2 硬件篇--数组和缓存 的有效利用
若存取的数据在内存中是连续的,缓存的作用就能得到更好发挥。但如果数据在内存中不是连续的,就会发生缓存未命中,从而增加存取时间。
下面通过一个例子来说明如何安排循环来提高缓存利用效率。
矩阵乘法
Cij=sigma(Aik*Bkj)
如果直接将这个式翻译为程序的话,结果如下:
for (int i = 0; i < 2; i++){
for (int k = 0; k < 2; k++){
for (int j = 0; j < 3; j++){
//设计循环时,注意到对数组b,当内层循环为对j的循环时,b[k][j]是连续的,
//同时在内层环中a[i][k]是一个固定值。而且调换顺序后,对结果并不产生影响。但计算速度会加快。但这只是理论上的分析,实际上,并没有发现快了。反而是慢了。
res[i][j] += a[i][k] * b[k][j];
//有时候为了提高速度可以考虑展开内层循环。
}
}
}
4.3 库函数篇
说到字符串比较,大家一般用的是strcmp函数,它是通过对字符串逐个字符进行比较来实现的,但如果要对比的字符串比较长的话,则执行时间也会比较长,这是个难点。
对于需要处理较长的字符串的情况,或字符串的问题比较大的情况,可以考虑将strcmp函数替换为memcmp函数以实现高效编程。
分析:strcmp是一个字符一个字符进行比较,而memcmp函数将需要比较的字符串分割成4个字节或8个字节一组的词为单位进行对比操作,这样就节约了时间。但请注意:这只在要比较的字符串的长度比较大时有效。如果字符串长度比较小,可能不如strcmp.
4.5 对比各种输入输出函数
行输入函数的比较
- fgets函数
【C语言】-->语法 fgets函数原理初探 - 省 - 博客频道 - CSDN.NET
http://blog.csdn.net/chenglibin1988/article/details/8738070fgets函数在运行过程中使用到了两个缓冲区,一个是stdio缓冲区,一个是用于存放用户输入的缓冲区。系统调用read把数据读入缓冲区stdiok ,fgets函数从stdio缓冲区中读取一行,或规定数量的内容到自己函数参数中规定的缓冲区。
缓冲区stdio的标准容量是4kb,但是可以使用setvbuf来扩容。
fgets函数原型为
从stream(通常是stdin)所指的文件读入字符到s所指的内存空间中,直到读到换行符、文件尾或n-1个字符为止,最后会加上NULL 作为字符串结束,即s[n-1] = NULL;如果在未读到n - 1个字符时,读到了换行符或者 文件结束标志(EOF),那么 就将换行符或者文件结束标志(EOF)都读到s中,此时,再在换行符或是文件结束标志(EOF)后面添加 NULL。 因为这个函数会将换行读入,所以一般不使用。还是用gets_s函数。char *fgets(char *s, int n, FILE *stream);
- getline函数
- 自定义块大小的行输入函数。 fread。
对于需要进行海量数据的输入输出的程序,需要对输入输出环节进行优化。因此一般,不要使用getline函数来读取数据,而是在自定义大小的输入输出函数中定义4MB的输入缓冲区,然后将数据直接读入缓冲区,需要时再取出一行的数据就可以了。
另外,原本我们在用字段分解输入行的过程中使用的是strtok函数,如果能将strspn函数和strcspn函数结合起来使用,效率将会得到提升。
C语言中strspn()函数和strcspn()函数的对比使用_C 语言_脚本之家
http://www.jb51.net/article/71441.htm性能优化上需要有整体的成本意识,尽量避免太依赖于运行环境或安装等固定成本,让其他人看到代码后也能尝试运行一下。
公共子表达式的消除(CSE: common sub-expression elimination)是编译器的优化方法之一,如果值相同的表达式在程序中出现多次,则只需要计算一次的一种运算方法。
》》如果一个函数不需要返回值,一定不要定义相应的返回值。
统计带小数部分的数
由于浮点数的计算相比整数类型计算要慢 ,而且浮点数与字符串之间的转换也很耗时,所以在需要大量进行浮点数计算时,可以将其转化为整数运算。
方案一:对所有数据规定一个最多小数位N,所有输入的数据在系统内部都保存成实际数据*10^N,这样可以将小数运算转化为整数运算。
方案二:如果对浮点数需要很高的精度,64位整数中能处理的十进制数最多19位,若超过这个位数,可以将小数和整数部分分开存放在两个64位整数中。例如:
23.333 在整数部分,存入23;小数部分存入333,当然也可以对小数部分进行扩大后存储。
60.1 整数部分60,小数部分100。
注意由于小数部分和整数部分进行了分开存放,所以在进行加法减法运算时,相应的进位和借位需要自己处理。所以虽然64位整数在十进制中可以有19位数,但在程序中的小数部分只能存放18位,最高位被当作进位和借位标志。
整数转换成字符串
sprintf函数可以将整数输出到字符串中。
判定字符的字节数
处理UTF-8字符串的程序必须先判定输入字符的字节数。UTF-8的字节数根据UNICODE的值定义为1-6个字节不等。但在2006年的iso中规定,不能对U+200000以上的字符位置进行分配,所以实际上UTF-8的字节数最大只能到4字节。
对于汉字而言,少数汉字占用3个字节,大多数汉字占用4个字节。
半角/全角字符和UNICODE
所谓半角字符和全角字符其实并没有标准定义,它是PC在性能低下时,限制字符表示为等宽字体(fixed width font)的术语。
对于一些特定的问题可以使用表格来减少计算量,或用来减少条件分支。
在UTF-8中ascii码的编码与原来一致,使用U+0000~U+007F.