新手都要会的逻辑代码优化

1,417 阅读4分钟

逻辑代码计算优化

在不考虑面向对象的情况下,先让我们先回忆下c语言中的3大结构。

  1. 顺序结构
  2. 分支(条件)结构
  3. 循环结构

这三个结构任意组合构成了我们书写的逻辑代码(算法)。下面我们就根据这常用的三种结构进行调优。注意,下文中的使用的语言为JavaScript,但考虑大的是大部分语言的情况。

代码在计算机中实际都是一条条语句执行的,分支和循环结构在使用了jump语句实现了语句跳转之后,实际上也变成了一个顺序结构。如果代码以顺序结构执行,则可以通过程序计数器自增让代码顺序执行。如果是jump跳转语句则会发生寻址,计算等额外步骤。

汇编

这里c语言代码中写了一个普通的循环。但是在计算机中实际执行时类似右边goto版本的。每一个goto会加重计算机的计算,所以在考虑性能的情况下尽量使用顺序结构。

优化循环

常见的循环有

  1. while
  2. for
  3. do...while

注意的是循环中while与for是先判断在开始循环,do...while是先运行一次后判断在开始循环

循环中两种方案调优

  • 减少每次循环处理的事务
  • 减少循环的次数

在循环判断条件中,通常我们将某个对象的属性进行判断比如

var i = 0;
for(;i < array.length;i++) {
  ...
}

实际上在计算机的语句为

  • 初始化(一次)
    1. 一次i初始化(var i)
    2. 一次i赋值(i = 0)
  • 判断(n次)
    1. i值查找(i)
    2. array值查找(array)
    3. 通过array找到length(array.length)
    4. i 与 length 比较(i < array.length)
  • 后处理,自增(n - 1次)
    1. i取值(i)
    2. 计算i + 1(i + 1)
    3. 将结果赋值给(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--;) {
  ...
}

这次实际上在计算机的语句为

  • 初始化(一次)
    1. i初始化(var i)
    2. array值查找(array)
    3. 通过array找到length(array.length)
    4. i赋值(i = array.length)
  • 判断(n次)
    1. i取值(i)
    2. 计算i - 1(i - 1)
    3. 将结果赋值给(i = i - 1)
  • 后处理,自增(n-1次)

实际上这样的语句确实少了挺多,但是必须是在顺序无关和数值比较的前提下。因为部分语言会提供数值与布尔值的自动转换(像java这么写就会报错)

减少使用递归

谈到循环,自然也会谈到递归。递归最简单说就是某个函数继续调用某个函数,一般是自己调用自己。

一个进程。有数据区,堆栈,PCB等内容。堆栈的大小在进程开启的时候基本确定,栈能存储运行时的局部变量,和函数调用信息。如果发生递归,则会在栈中持续添加内容。若执行函数需要用到局部变量,或者调用函数不是最后一句时。将会发生中断。中断后,本次运行的上下文环境也会被保存进栈内,形成一个调用栈。

递归

发生一次中断,会触发CPU的中断处理,保存中断信息现场,指令跳转。恢复时又会跳转指令,恢复中断现场信息。如果非必要,请不要使用递归。

栈的大小是固定的,如果超过栈的大小,就会有爆栈的可能(著名的stack overflow)。部分语言有尾调用优化,可以让递归不保存中断信息。 尾调用优化-阮一峰

优化选择

常见的选择有

  • if...else 更适合范围匹配
  • switch...case 更适合单值匹配

不管使用的是if...else还是switch...case,判断都是按书写的顺序开始执行的。所以我们应该把最可能出现的情况放在第一位。

由于选择结构也会触发指令跳转,我们应该使用嵌套的if...else,使用树的形式,让到达每一个结果的情况尽可能的少。