引言
代码优化也是有追求的工程师津津乐道的话题,但不同的程序设计语言的代码优化姿势可能千差万别,本文试图给出一些切实可行而与语言无关的优化规则参考,并简述其原理。如果涉及到特定语言的XXX,请参考XXX 性能优化 高效XXX类似的书籍。
代码优化的说法有不少,现在印象比较深的有两个:
但个人认为上面的说法都是片面的,参考意义不大。
为什么这么说呢?高质量的代码需要从设计的时候就要考虑了,而且每一步都可以随手优化(比如我们下面会提到的规则)。如果当事人的知识或经验积累还没有达到类似的程度,可能的一个结果是在整体代码完成后发现有类似性能指标达不到的问题,这个时候要做的是分析相关问题的瓶颈,然后给出优化调整。这样一来项目的人力资源投入将会不同程度的增加,而且可能还会影响软件交付。当这个当事人再遇见类似的场景时,估计就不会相信上面两种说法的字面意思了。
如果我们每个阶段都是局部最优的状态产出,绝大部分情况下不会有类似的指标达不到的问题,如果确实还是出现了这样的情况,这才是我们真正的挑战。当然我们的优化必须遵循一定的规则:
-
等价原则
(这不废话吗?看起来像)
别将正确的代码优化成不正确了,别以为测试了某些条件就完全等价了
正是这个规则,让我们对优化有很大的操作空间
-
有效原则
只要能满足我们的指标要求
可以是任何的时间换空间,空间换时间
不能优化变劣化,比如跟编译器的优化规则冲突,导致优化后的结果更糟糕
-
合算原则
尽可能降低投入产出比,以较低的代价获取较好的效果
可以适当的选择不优化,比如就某些场景下会不达标,规则可商量
-
自愿原则
如果这些规则束缚了你,大可以完全忽略
都是一些常理之中的简单规则,比如让使用最频繁的代码尽可能的高效,让多步操作尽可能一步完成,不做多次完全相同的计算等。
贯穿始终的一个思想是缓存,缓存计算结果,缓存获取的数据等。
循环优化
-
将循环变量无关的计算都放到循环外
-
尽可能减少循环内的操作数
-
在循环外使用局部变量,缓存多步操作的结果
-
将一次性的计算逻辑从循环中挪出来作为一次性计算结果
-
外提循环内的各种条件判断
-
循环中结构中的循环元素无关的逻辑判断条件的计算,比如 if(a > b && b > c)改为if(conditionX)
-
循环判断条件的计算,比如i
-
交换内外循环,将次数多的循环放在最内层
-
减少循环次数,降低循环切换代价
-
利用前面循环的结果
-
比如记录一个变量,每次循环增加值,利用这个变量使用加法算本次的结果,而不是使用循环来从头计算
-
将多次全循环合并(可能降低可读性)
如果可能的话讲多个可相关循环的操作放到一个循环中
光说不练假把式,每种语言运行环境都有各自的表达方式,这里用“最简单“的JavaScript做一个演示。
var i,len,testData = [],testCount = 9999,repeatCount = 999;
function doInit(testData){
for(var i = testCount; i--;){
testData[i] = i;
}
}
function doRepeat(operation,data){
for(var i = 0; 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');
多次执行上面的代码查看效果,结果如下: