原文:http://www.cnblogs.com/techsunny/archive/2008/03/24/1120380.html
以下对程序性能优化的相关知识做了总结。应注意的是没有放之四海皆准的方法。
1.策略
1.1.最重要的原则:尽量少用代码优化
(1) 相对于精细的代码优化,程序架构、数据结构、算法和细节设计更重要。
(2) 不成熟的代码优化会危及程序的正确性、功能性和可维护性。
(3) 有时充满技巧的代码不利于编译程序做优化。
因此,优化前要先确定代码优化的必要性。
1.2.最关键的环节:剖析
(1) 80/20原则:大部分的时间消耗可能集中在很少数的几个子程序上。
(2) 消除并不“最热”的热点实际上不能用程序性能得到实质性的提升。不要优化无关紧要的东西。
(3) 性能问题的很多方面是违反直觉的。
(4) 经验没有太大帮助。语言、编译器、平台等条件改变时,经验也会过时。
1.3.代码优化的时机
(1) 优化应放在整个系统完成后进行。
(2) 在程序所有功能运转之前难以确定性能的瓶颈。
(3) 过早优化可能顾此失彼,忽略整个系统全局性的重要优化。
(4) 过早优化会妨碍对程序其它目标的理解和判断。
总结一点就是:过早优化的主要缺陷在于缺乏前瞻性。
1.4.代码优化的步骤
1. 用良好的设计开发软件,使程序易于理解和修改。
2. 若程序性能无法达到要求:
a. 保存程序能正确运行的版本。
b. 剖析系统,找出热点。
c. 判断性能的拙劣是否源于设计、数据结构或算法上的缺陷,确定是否应做代码优化。如果不是,回到步骤1。
d. 对瓶颈代码进行调整。
e. 每次调整后都对性能进行剖析。
f. 如果调整没有改进代码的性能,回到步骤2所保存的代码。
3. 重复步骤2。
1.5.其它原则
(1) 程序的正确性比运行速度更重要。
(2) 应尽量使用高效的算法,而不是去优化一个效率低下的算法。
(3) 尽管大部分优化方法单独看来收效甚微,但累计起来效果惊人。
(4) 优化应尽可能保持与硬件无关,并且对于其他操作系统是可移植的。不针对特定平台优化程序。
(5) 优化应使代码易于修改。
(6) 优化应该使程序性能的提高不小于20%。
2.技术
2.1.常见的低效之源
(1) 输入/输出操作。
(2) 系统调用,通常会涉及上下文切换。
(3) 解释型语言。
(4) 错误。
2.2.用空间换取时间
(1) 扩展数据结构:给数据结构增加其他信息或改变结构的内部信息让它访问的更快。
(2) 用查表代替昂贵函数的计算:提前计算好运行时载入,或只在运行时计算一次。
(3) 高速缓存:降低经常访问数据的访问成本。
(4) 惰性求值:直到需要时才会计算某个元素,可避免计算不必要的元素。
2.3.用时间换取空间
(1) 压缩:密集存储表示能够通过增加存储和检索所需的时间来降低存储成本。
(2) 解释程序:通常,使用解释程序能减少表示程序所需的空间,解释程序压缩表示相同的操作序列。
2.4.存储
(1) 数组维度尽可能少。
(2) 尽可能减少数组访问。
(3) 对齐数据源地址。
(4) 成组进行读写操作。
(5) 消除数据相关性。
(6) 同时向存储控制器发送多个查询。
(7) 请求按不少于32个字节的增量方式读取数据。
(8) 使用所有经历请求的页面。
(9) 组合执行存取内存的代码。
(10) 仅仅在必要时才放问内存。
(11) 写专用的存储分配程序,考虑采用内存池技术。
(12) 缓存机制,对输入输出做缓冲。
2.5.循环
(1) 移出循环不变量。
(2) 循环展开。
(3) 循环合并。
(4) 循环内部尽量少包含测试条件:合并测试条件,将循环内判断外提,哨兵。
(5) 减少循环内部的工作。
(6) 最忙的循环放在最内层。
(7) 传输驱动的循环展开:成本很高的内部循环中,可通过改变使用的变量消除一些赋值,如:删除赋值i=j,后面的代码将j视为i。
2.6.逻辑
(1) 利用代数恒等式,用低价的操作代替高价的,如:用左或右移位实现幂的乘或除;使用加法替代乘法;减少数组元素上的循环迭代。
(2) 删除公共子表达式。
(3) 知道答案后立即停止判断。
(4) 调整判断次序:将廉价的经常成功的判断放在昂贵的很少成功的判断前面。
(5) 表驱动代替复杂判断:可用查找替代一个小的、有限域中的逻辑函数。
2.7.过程
(1) 使用宏或内联函数。
(2) 利用常见情况:正确处理所有情况,并高效处理常见情况。
(3) 用迭代改写递归,消除尾递归(通常编译器会做此优化) 。
(4) 尽可能地利用并发:充分使用基本计算机体系结构的数据总线宽度来计算昂贵的表达式。
(5) 配对计算:若总是同时计算两个类似的表达式,应放到一个过程中成对地计算。
2.8.汇编
(1) 不应将代码优化与汇编实现混为一谈。检测到热点后,首先应尽量在高级语言中采取可能的步骤。
(2) 在用汇编重写代码前,先估算编译器生成汇编代码的效率。改善由编译器生成代码的性能比从零开始用汇编实现要容易得多。
(3) 如果编译器生成的汇编代码虽然显得很不错,但运行起来仍很慢,可考虑加载到反汇编工具中。
(4) 如果可用的处理器指令能实现比较高效的算法,可直接着手实现汇编代码。
(5) 在开发汇编代码时,不管存在什么样的干扰,都应给出一个精巧而高效的方案。
(6) 应将所有汇编函数存放在单独的模块中。避免使用内联汇编函数,这些函数很少是可移植的,且移植时经常会带来负面效果。
2.9.其它
(1) 数学方法解决问题。
(2) 使用近似值。
(3) 避免使用浮点数,尽量使用整数。
(4) 将8位、16位的循环变量替换为32位的。
(5) 用memset() 代替初始化数组的循环,用memcpy() 代替拷贝数组的循环。
(6) 尽量减小数组元素的大小,以减少Cache失效率。
(7) 循环条件判断式中不要有其它运算:while (i--) {}; --> while (i) { i--; };
(8) 以行序而不是列序迭代数组。
3.参考文献
[1]《代码大全》. Steve McConnell 著, 金戈 等译. 电子工业出版社. p587-645
[2]《编程矶珠》. Jon Bentley 著, 谢君英 等译. 中国电力出版社
[3]《代码优化:有效使用内存》. Kris Kaspersky 著, 谭明金 译. 电子工业出版社
[4]《C代码优化方案》. 王全明整理. 网上资料
[5] VTune帮助文档