本章重点讲述提高代码运行速度的方法,同时也对如何减少代码的资源占用给出了一些建议。
程序性能通常同代码的速度和资源占用相关,但减少代码资源占用更主要的是通过对类和数据结构的重新设计来实现,而非代码调整。代码调整更多的是指小规模的修改,而非大范围的设计变更。
本章没有什么放止四海皆准的方法,因此不要在自己程序中照葫芦画瓢;主要目的是示范一些代码调整方法,似乎同24章“重构”相似,但重构是去改善内部结构。本章所述内容将其称之为“反重构”或许更恰如其分,这种改变是以牺牲程序内部结构的某些特性来换取更高的性能。
盲目采纳各种代码调整很危险,一定要用测试数据证明,适合的才是最好的
不要把代码调整当作灵丹妙药,唯一可以信赖的法则就是每次都应当在具体的环境下评估代码调整带来的效果,也就是所谓的只有合适的才是最好的。
26.1 逻辑——因势利导使用逻辑表达式
- 知道答案后就停止判断;
- 按照出现频率调整判断顺序,让运行最快和判断结果最有可能为真的判断首先被执行;
- 相似逻辑结构性能比较时,实际测量出的结论才是最合适的,代码调整没有所谓的某种“黄金法则”或是“逻辑准则”
- 查询表代替复杂表达式
-
26.2 循环
将判断外提
切换:指循环中的判断,该判断每次循环中都会被执行。
如果在循环运行时某个判断结果不会改变,你就可以把这个判断提到循环外面,避免在循环中进行判断。
示例调整前:
示例调整后:
进一步的调整,可以将判断里执行频率高的类似逻辑抽成一个公共方法,减少代码量,也利于维护。
不同语言调整后的提升对比:
启示:必须通过测量每一种特定优化方法带来的性能提升来确定优化效果——没有例外合并或融合
把两个对相同一组元素进行操作的循环合到一起
风险点: 考虑要合并的两个循环各自的下标有可能改变
- 保证合并后代码的执行顺序仍然正确
展开
将循环里的执行语句单句拆分成多句,感觉平时使用较少。尽可能减少循环内部逻辑
如果可以在循环外面计算某语句或某部分语句,而在循环内部只是使用计算结果,那么就把它放在外面。哨兵值
当循环的判断条件是一个复合判断的时候,可以通过简化判断节省代码运行时间。如果循环是一个查找循环,简化方法之一就是使用哨兵值,可以把他放在循环范围的末尾,从而保证循环一定能够中止。
引入哨兵值后的:把最忙的循环放在外侧
削弱强度
用多次轻量级运算(例如加法)来代替一次代价高昂的运算(例如乘法)26.3 数据变换——同样可成为减少程序规模和改进执行速度方面的利器
使用整型数而不是浮点数
整型数的加法和乘法要比浮点数运算快很多。数组维度尽可能少
尽可能减少数组引用
在内层循环中,虽然discountLevel发生了改变,但对discount[ discountType ]的引用没有变,所以可以提出来,修改后:使用辅助索引
使用缓存机制
26.3 表达式
利用代数恒等式——用低代价操作代替复杂操作
```cpp 示例一: // 修改前 not a and not b // 调整后 not (a or b)
示例二: sqrt(X) < sqrt(y) 由于只有当x小于y的时候,sqrt(X)才会小于sqrt(y),因此可以用 x < y 代替 ```
削弱运算强度
- 加法代替乘法
- 乘法代替幂乘
- 定点数或整型数代替浮点数
-
编译期初始化
子程序中使用了一个具名常量或是神秘数值,而且它是子程序的唯一参数,这就是一种暗示,应当提前计算这一数值,放到常量中,从而避免子程序调用
小心系统函数
使用正确的常量类型
当常量和相关变量的类型不一致时,那么编译器就不得不对常量进行类型转换,然后才能将其赋给变量。优秀的编译器可能瞬间就完成转换,稍逊点的编译器就会碰到麻烦。
预先算出结果
删除公共子表达式
当某个表达式老是出现在程序中,就把它赋给一个变量,然后在需要的地方引用该变量,而非重新计算这个表达式。
26.5 子程序
代码调整的利器之一就是良好的子程序分解。短小、定义明确的子程序能够代替多处单独执行相同操作的代码,因此能够节省空间。而且使得优化更加简单,因为重构某子程序就可以惠及各处。
26.6 必要时用低级语言重写代码
更古不变的箴言:当程序遭遇性能瓶颈的时候,你应当用低级语言重写代码。
26.7 变得越多,事情反而越没变
作者本章说到在写《代码大全》第1版之后的十年时间里,系统性能可能会以某种方式发生了变化,计算机的运行速度也发生了翻天覆地的变化,可用内存对很多程序而言绰绰有余。在第1版运行本章测试一万到5万次,而在第2版测试会运行一百万次到一亿次,当某个测试需要跑上亿次才能得出测量结果时候,你不得不产生疑问,有谁会注意这些优化工作对实际程序所产生的影响。如今计算机这么强悍,对很多常见类型的程序来说,本章所讨论的性能优化提升的意义已如昨日黄花。
每一次代码调整所产生的影响进行量化评估已经成了性能优化工作的永恒信条
- 代码调整无可避免地为性能改善的良好愿望而付出复杂性、可读性、简单性、可维护性方面的代价
- 恪守“对每一次的改进进行量化”的准则,是抵御思考成熟前匆忙优化之诱惑的法宝;未经量化测量的代码优化对性能上的优化充其量是一次投机,对可读性等产生的负面影响则确凿无疑。
要点总结:
优化结果在不同的语言、编译器和环境下有很大差异;如果没有对每一次的优化进行测量,你将无法判断优化到底是好还是坏。
第一次优化通常不会是最好的。即使找到了效果和不错的,也不要停下扩大战果的步伐。
如果你决定使用本章所述调整方法,请务必谨慎行事。
核对表
本章思维导图
思维导图链接: https://www.yuque.com/qbp95u/sx1dgb/eezmdc?inner=mdjVM