文档章节

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

zray4u
 zray4u
发布于 2016/07/04 17:17
字数 2588
阅读 116
收藏 2

变量与寄存器

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

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

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

变量间的乘法运算

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

© 著作权归作者所有

共有 人打赏支持
zray4u
粉丝 0
博文 216
码字总数 188982
作品 0
西城
私信 提问
Golang学习笔记目录

Golang 介绍 Go语言是谷歌2009发布的第二款开源编程语言。 Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。...

ChainZhang
2017/12/26
0
0
Cython 三分钟入门

作者:perrygeo 译者:赖勇浩(http://laiyonghao.com) 原文:http://www.perrygeo.net/wordpress/?p=116 我最喜欢的是Python,它的代码优雅而实用,可惜纯粹从速度上来看它比大多数语言都要...

鉴客
2012/02/23
8.7K
4
读书笔记:明朝那些事儿(第三部)

明朝那些事儿 哲理 在中国历史上,臭名昭著的程度足以与此句匹敌的只有那句“莫须有”。 “莫须有”杀掉了岳飞,“意欲”杀掉了于谦。 真正的政治老手是不同于常人的,他们炒菜时从来不用大火...

Prioro
2017/11/23
0
0
道德经的读书笔记范文3300字

道德经的读书笔记范文3300字: 浩峰按:因为阅读塔勒布的《反脆弱》,感觉到其思想和我国古代老子的“无为而无不为”思想相通,所以,再次阅读古今对照的《道德经》,希望从中能发现一些源远...

原创小博客
03/26
0
0
寻找“最好”(2)——函数和泛函的拉格朗日乘数法

拉格朗日乘数法   大多数的优化问题都会加入特定的约束,而不仅仅是指定起点和终点,此时需要更好的办法去解决优化问题,拉格朗日乘数法正是一种求约束条件下极值的方法。   简单地说,拉...

我是8位的
2018/08/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

分布式项目(五)iot-pgsql

书接上回,在Mapping server中,我们已经把数据都整理好了,现在利用postgresql存储历史数据。 iot-pgsql 构建iot-pgsql模块,这里我们写数据库为了性能考虑不在使用mybatis,换成spring jd...

lelinked
今天
2
0
一文分析java基础面试题中易出错考点

前言 这篇文章主要针对的是笔试题中出现的通过查看代码执行结果选择正确答案题材。 正式进入题目内容: 1、(单选题)下面代码的输出结果是什么? public class Base { private Strin...

一看就喷亏的小猿
今天
1
0
cocoapods 用法

cocoapods install pod install 更新本地已经install的仓库 更新所有的仓库 pod update --verbose --no-repo-update 更新制定的仓库 pod update ** --verbose --no-repo-update...

HOrange
今天
3
0
linux下socket编程实现一个服务器连接多个客户端

使用socekt通信一般步骤 1)服务器端:socker()建立套接字,绑定(bind)并监听(listen),用accept()等待客户端连接。 2)客户端:socker()建立套接字,连接(connect)服务器,连接上后...

shzwork
昨天
3
0
android自定义viewgroup画背景

设计部要求背景实现一个背景边框带圆弧的效果: 所以想着用自定义控件画一个背景。 为了方便,继承的是LinearLayout,在onMeasure中先获取控件宽高: @Overrideprotected void onMeasure(in...

醉雨
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部