逻辑代码计算优化
在不考虑面向对象的情况下,先让我们先回忆下c语言中的3大结构。
- 顺序结构
- 分支(条件)结构
- 循环结构
这三个结构任意组合构成了我们书写的逻辑代码(算法)。下面我们就根据这常用的三种结构进行调优。注意,下文中的使用的语言为JavaScript,但考虑大的是大部分语言的情况。
代码在计算机中实际都是一条条语句执行的,分支和循环结构在使用了jump语句实现了语句跳转之后,实际上也变成了一个顺序结构。如果代码以顺序结构执行,则可以通过程序计数器自增让代码顺序执行。如果是jump跳转语句则会发生寻址,计算等额外步骤。
这里c语言代码中写了一个普通的循环。但是在计算机中实际执行时类似右边goto版本的。每一个goto会加重计算机的计算,所以在考虑性能的情况下尽量使用顺序结构。
优化循环
常见的循环有
- while
- for
- do...while
注意的是循环中while与for是先判断在开始循环,do...while是先运行一次后判断在开始循环
循环中两种方案调优
- 减少每次循环处理的事务
- 减少循环的次数
在循环判断条件中,通常我们将某个对象的属性进行判断比如
var i = 0;
for(;i < array.length;i++) {
...
}
实际上在计算机的语句为
- 初始化(一次)
- 一次i初始化(var i)
- 一次i赋值(i = 0)
- 判断(n次)
- i值查找(i)
- array值查找(array)
- 通过array找到length(array.length)
- i 与 length 比较(i < array.length)
- 后处理,自增(n - 1次)
- i取值(i)
- 计算i + 1(i + 1)
- 将结果赋值给(i = i + 1)
这次判断实际上有四个语句组成,在看看下面的代码
var i = 0;
var len = 0;
for(len = array.length;i < len;i++) {
...
}
在这个判断中实际上在计算机的语句为
- 一次i值查找。
- 一次len值查找
通过array找到length- i 与 length 比较
这里在单次循环中语句减少了一条。在多次循环下,将减少了n条计算机语句
如果是简单的数值比较我们还可以使用倒序循环方式
var i = array.length;
for(;i--;) {
...
}
这次实际上在计算机的语句为
- 初始化(一次)
- i初始化(var i)
- array值查找(array)
- 通过array找到length(array.length)
- i赋值(i = array.length)
- 判断(n次)
- i取值(i)
- 计算i - 1(i - 1)
- 将结果赋值给(i = i - 1)
- 后处理,自增(n-1次)
实际上这样的语句确实少了挺多,但是必须是在顺序无关和数值比较的前提下。因为部分语言会提供数值与布尔值的自动转换(像java这么写就会报错)
减少使用递归
谈到循环,自然也会谈到递归。递归最简单说就是某个函数继续调用某个函数,一般是自己调用自己。
一个进程。有数据区,堆栈,PCB等内容。堆栈的大小在进程开启的时候基本确定,栈能存储运行时的局部变量,和函数调用信息。如果发生递归,则会在栈中持续添加内容。若执行函数需要用到局部变量,或者调用函数不是最后一句时。将会发生中断。中断后,本次运行的上下文环境也会被保存进栈内,形成一个调用栈。
发生一次中断,会触发CPU的中断处理,保存中断信息现场,指令跳转。恢复时又会跳转指令,恢复中断现场信息。如果非必要,请不要使用递归。
栈的大小是固定的,如果超过栈的大小,就会有爆栈的可能(著名的stack overflow)。部分语言有尾调用优化,可以让递归不保存中断信息。 尾调用优化-阮一峰
优化选择
常见的选择有
- if...else 更适合范围匹配
- switch...case 更适合单值匹配
不管使用的是if...else还是switch...case,判断都是按书写的顺序开始执行的。所以我们应该把最可能出现的情况放在第一位。
由于选择结构也会触发指令跳转,我们应该使用嵌套的if...else,使用树的形式,让到达每一个结果的情况尽可能的少。