参考书籍《深入理解计算机系统》
1 高效程序的基础
- 数据结构和算法:比如hash_map的查找效率会高于map;1加到100用公式(1 + 100)* 50的效率高于循环累加
- 源代码能够被编译器有效优化成高效可执行代码:对于这一点需要理解优化编译器的能力与局限性,C的一些特性——比如执行指针运算和强制类型转换等——使得优化很困难
2 标题编译过程
- 预处理生成.i文件(预处理器);
- 将预处理后的文件转换成汇编语言,生成.s文件(编译器);
- 将汇编转换成机器代码,生成.o文件(汇编器);
- 链接目标代码,生成可执行程序(链接器)。
3 优化编译器的能力和局限性
-
编译优化:编译优化会更改代码的,在优化中,有不同的选项,有些是牺牲时间换空间,有些是牺牲空间换时间;编译优化非常繁杂和易错,最激进的优化策略可能会带来错误,如果想采用最激进的策略进行优化,编码阶段就必须有编码规范约束,对开发人员的要求会较高
注:这部分内容可以看一下编译原理,东西比较多,这里只介绍简单的概念 -
编译优化等级
-O0 无优化;
-O1 基本优化,编译器会在不花费太多编译时间(不显著增加)的同时试图 生成更快更小的代码;
-O2 包含-O1的优化并增加了不需要在目标文件大小和执行速度上进行折衷的优化,此选项将 增加编译时间和目标文件的执行性能 (编译器不执行循环展开以及函数内联),此选项是推荐的优化等级;
-Os(-O2.5)在 -O2 选项的基础上(基本包含所有,除了对齐优化等),执行 优化程序空间大小 的优化选项,专门优化目标文件大小。可能产生些许问题,不推荐的优化等级;
-O3 这是最高最危险的优化等级,用这个选项会延长编译代码的时间,产生更大体积更耗内存的二进制文件,大大增加编译失败的机会或不可预知的程序行为(包括错误),在 gcc4.x 中使用-O3是不推荐的。
注1:编译优化的具体实现跟编译器也有关系,本文所有编译器如未特殊说明,均指gcc编译器
注2:不同编译优化等级其实是不同编译优化选项的一些集合,比如-floop-optimize(优化循环)、-fif-conversion(优化if-then语句)等 -
编译优化的局限性:
1.可能会改变正确的程序行为;
2.编译器对程序的行为、使用环境了解有限;
3.会增加编译时间。
4 局部性原理
- 含义:计算机程序倾向引用的数据项邻近于其他最近引用过的数据项,或者邻近于最近自我引用过的数据项的这种特性称为局部性,局部性通常有两种形式:时间局部性和空间局部性;
- 时间局部性:在一个具有良好时间局部性的程序中,被引用过一次的存储器位置很可能在不远的将来再被多次引用;
- 空间局部性:在一个具有良好空间局部性的程序中,如果一个存储器位置被引用了一次,那么程序很可能在不远 的将来引用附近的一个存储器位置;
- 应用:有良好局部性的程序比局部性差的程序运行得更快,现代计算机系统得各个层次,从硬件到操作系统、到应用程序,对局部性得利用如下:
1.硬件层:局部性原理允许计算机设计者通过引入称为高速缓存存储器得小而快得存储器来保存最近被引用得指令和数据项,从而提高对主存得访问速度;
2.操作系统:系统使用主存作为虚拟地址空间最近被引用块得高速缓存,类似的,操作系统用主存缓存磁盘文件系统中最近被使用的磁盘块;
3.应用层:例如web浏览器将最近被引用的文档放在本地磁盘; - 对程序数据引用的局部性:比如对二维数组的按行优先顺序读取,能得到一个很好的步长为1的引用模式,则这种访问方式就具有良好的空间局部性,空间局部性随着访问的步长的增加而下降;
- 取指令的局部性:拿for循环体来说,其指令是按照连续的存储器顺序执行的,因此循环具有良好的空间局部性,又因为循环体会被多次执行,所以它也有很好的时间局部性。区别于程序数据的属性是在运行时指令是不能被修改的,当程序正在执行时,CPU只从存储器中读出它的指令,CPU不会重写或修改这些指令;
- 量化评价局部性的简单原则:
1.重复引用同一个变量的程序具有良好的时间局部性;
2.对具有步长为k的引用模式的程序,步长越小,空间局部性越好;
3.对于取指令来说,循环有好的时间和空间局部性,循环体越小,迭代次数越多,局部性越好(适当的循环拆解能提升效率)。
5 程序优化的方法
- 消除低效的循环:把不必要的语句提到循环外面
- 降低循环开销:展开循环
- 减少过程调用
- 消除不必要的存储器引用:引入临时变量来保存中间结果,只有在最后值计算出来时才将结果存放在数组或全局变量中;
- 利用指针和数组
- 提高并行性:分割循环
6 程序性能剖析工具
- Unix提供GPROF
功能:这个程序可提供两种形式的信息:程序中每个函数花费了多少CPU时间;每个函数被调用的次数
使用步骤:
1.加上编译命令行选项 -pg:gcc -O2 -pg prog.c -o prog
;
2.正常执行程序:./prog
,运行会比正常的稍微慢一点,会生成一个gmon.out文件;
3.调用GPROF来分析gmon.out中的数据:gprof prog
注:GPROF的计时是基于简单的间隔计数机制,不是很准确;默认情况下不会对库函数的调用