笔记——《C程序性能优化》[日】片山善夫

变量与寄存器

未指定优化选项时,编译器会将程序中的变量储存到内存上,程序在执行相关运算时会根据需要装载到寄存器,当运算结束后再将运算器上的内容移到内存,然后生成目标代码。

但如果指定了优化选项,变量就会尽可能地储存在寄存器上,从而减少访存时间。

===============

变量间的乘法运算

和加法运算相同,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/8738070

fgets函数在运行过程中使用到了两个缓冲区,一个是stdio缓冲区,一个是用于存放用户输入的缓冲区。系统调用read把数据读入缓冲区stdiok ,fgets函数从stdio缓冲区中读取一行,或规定数量的内容到自己函数参数中规定的缓冲区。

缓冲区stdio的标准容量是4kb,但是可以使用setvbuf来扩容。

fgets函数原型为

char *fgets(char *s, int n, FILE *stream);
从stream(通常是stdin)所指的文件读入字符到s所指的内存空间中,直到读到换行符、文件尾或n-1个字符为止,最后会加上NULL 作为字符串结束,即s[n-1] = NULL;如果在未读到n - 1个字符时,读到了换行符或者 文件结束标志(EOF),那么 就将换行符或者文件结束标志(EOF)都读到s中,此时,再在换行符或是文件结束标志(EOF)后面添加 NULL。 因为这个函数会将换行读入,所以一般不使用。还是用gets_s函数。
  • 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.

转载于:https://my.oschina.net/ray1421/blog/705365

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值