引言

前面谈到了Web性能优化的思路和参考点,没有涉及到具体可实施的代码层面的内容。代码优化也是有追求的工程师津津乐道的话题,但不同的程序设计语言的代码优化姿势可能千差万别,本文试图给出一些切实可行而与语言无关的优化规则参考,并简述其原理。如果涉及到特定语言的XXX,请参考XXX 性能优化 高效XXX类似的书籍。
代码优化概述
代码优化的说法有不少,现在印象比较深的有两个:
不要优化,还是不要优化不要过早优化但个人认为上面的说法都是片面的,参考意义不大。为什么这么说呢?高质量的代码需要从设计的时候就要考虑了,而且每一步都可以随手优化(比如我们下面会提到的规则)。如果当事人的知识或经验积累还没有达到类似的程度,可能的一个结果是在整体代码完成后发现有类似性能指标达不到的问题,这个时候要做的是分析相关问题的瓶颈,然后给出优化调整。这样一来项目的人力资源投入将会不同程度的增加,而且可能还会影响软件交付。当这个当事人再遇见类似的场景时,估计就不会相信上面两种说法的字面意思了。如果我们每个阶段都是局部更优的状态产出,绝大部分情况下不会有类似的指标达不到的问题,如果确实还是出现了这样的情况,这才是我们真正的挑战。当然我们的优化必须遵循一定的规则:等价原则(这不废话吗?看起来像)别将正确的代码优化成不正确了,别以为测试了某些条件就完全等价了正是这个规则,让我们对优化有很大的操作空间有效原则只要能满足我们的指标要求可以是任何的时间换空间,空间换时间不能优化变劣化,比如跟编译器的优化规则冲突,导致优化后的结果更糟糕合算原则尽可能降低投入产出比,以较低的代价获取较好的效果可以适当的选择不优化,比如就某些场景下会不达标,规则可商量自愿原则如果这些规则束缚了你,大可以完全忽略代码优化规则参考
都是一些常理之中的简单规则,比如让使用最频繁的代码尽可能的高效,让多步操作尽可能一步完成,不做多次完全相同的计算等。贯穿始终的一个思想是缓存,缓存计算结果,缓存获取的数据等。
循环优化
将循环变量无关的计算都放到循环外尽可能减少循环内的操作数在循环外使用局部变量,缓存多步操作的结果将一次性的计算逻辑从循环中挪出来作为一次性计算结果外提循环内的各种条件判断循环中结构中的循环元素无关的逻辑判断条件的计算,比如 if(a > b b > c)改为if(conditionX)循环判断条件的计算,比如i < xxxList.length 改为 i < xxxListLen交换内外循环,将次数多的循环放在最内层减少循环次数,降低循环切换代价利用前面循环的结果比如记录一个变量,每次循环增加值,利用这个变量使用加法算本次的结果,而不是使用循环来从头计算将多次全循环合并(可能降低可读性)如果可能的话讲多个可相关循环的操作放到一个循环中逻辑判断优化
switch 语句按照出现频率排序,让最常用的放在最前面,减少无效判断if 语句将弱计算的条件放在前面,避免过多的计算(短路规则)逻辑判断条件转换为与0值比较使用结果缓存,而不是每次都计算结果使用查表的 *** 代替复杂的条件计算表达式优化
公共的表达式用局部变量替代布尔逻辑优化,比如!a !b 转换为!(a || b) 会减少计算使用自增、自减表达式降低计算强度用加法代替乘法用乘法代替幂乘使用移位代替整数2的倍数的乘除使用位运算代替取余运算类型、结构和算法的选择
使用尽量简单的数据类型使用int 或long 代替 long long使用定点或整数代替浮点数使员工单精度单体双精度尽可能降低数组维度尽可能减少数组的引用使用更高效的算法比如二分查找代替顺序查找快速排序代替插入排序和冒泡排序优化规则评价
光说不练假把式,每种语言运行环境都有各自的表达方式,这里用“最简单“的JavaScript做一个演示。
var i,len,testData = [],testCount = 9999,repeatCount = 999function doInit(testData){ for(var i = testCount i--){ testData[i] = i }}function doRepeat(operation,data){ for(var i = 0 i < repeatCount;i++){ operation(data); }}function origin(data){ var ret = []; for(var i = 0; i < data.length; i++){ ret[i] = data[i] * data[i]; }}function optLen(data){ var ret = []; for(var i = 0,len = data.length; i < len; i++){ ret[i] = data[i] * data[i]; }}function optIdx(data){ var ret = [],tmpData; for(var i = 0,len = data.length; i < len; i++){ tmpData = data[i]; ret[i] = tmpData * tmpData; }}function optCmp(data){ var ret = []; for(var i = data.length; i-- > 0){ ret[i] = data[i] * data[i] }}doInit(testData)console.time(\"origin\")doRepeat(origin,testData)console.timeEnd('origin')console.time('optmize len')doRepeat(optLen,testData)console.timeEnd('optmize len')console.time('optmize index')doRepeat(optIdx,testData)console.timeEnd('optmize index')console.time('optmize compare')doRepeat(optCmp,testData)console.timeEnd('optmize compare')
多次执行上面的代码查看效果,结果如下:在Safari 10.1 (12603.1.30.0.34) 下的运行结果(基本稳定):
origin: 163.225msoptmize len: 139.275msoptmize index: 118.999msoptmize compare: 248.611ms
在Chrome 58.0.3029.96 下的运行结果(不太稳定):
origin: 81.718017578125msoptmize len: 77.73486328125msoptmize index: 74.80517578125msoptmize compare: 378.320068359375ms
早Node *** 7.9.0下的运行结果(不太稳定):
origin: 110.480msoptmize len: 104.123msoptmize index: 98.463msoptmize compare: 383.104ms
上面结果看来,优化循环判断条件和数据缓存都有一定的效果;但最后一种 *** 的写法,看起来少了一个局部变量,但执行结果是成倍的劣化(恶化)。这曾经也是一种代码优化的方式,作者也深中其毒,但这已经不那么重要了。现在问题来了,为什么会这么劣化,是一个值得研究的问题。
参考资料
《代码大全》第二版《编程匠艺-编写卓越代码》《程序设计语言编译原理》《深入理解计算机系统》 第二版《程序设计 *** 与优化》《编程珠玑》《高效程序的奥秘》《算法心得-高效算法的奥秘》代码优化与性能调优 高性能计算与系统优化