Switch 语句在虚拟机中的使用

大学时说switch 是逐个比较,害我一直记到现在。

我信你奶奶个腿儿!

从数组说起

毫无疑问,一般if:else 都是逐个比较,所以条件多了,效率会比较低。于是人们想到了各种算法和数据结构来优化这个问题,比如二分法、树、哈希表等。
也有switch:case 语句,大学时讲这个switch:case 更像是if:else 的语法糖,最后还是逐个比较,只是写起来比较好看。

但是,如果switch 的条件如果是非负整数或者是枚举呢?是不是刚好就像是数组中的索引,而通过索引定位元素可是最快的(没有之一哦)。

以下面c/llvm 代码为例:

// C 代码
switch(op) {
    case 0: ...
    // ...
    case 100: ...
}

对应llvm 代码如下:

switch i32 %op, label %default [
  i32 0, label %case0
  i32 1, label %case1
  ...
]

llvm 四种switch 降级方案

跳转表O(1)

如果分支连续(或者密度足够高),编译器会产生一个指针数组,根据操作数,直接跳转到数组元素代表的地址:

cmp op, max
ja default
jmp [jump_table + op * 8]

二叉查找树O(logn)

如果分支比较稀疏,且范围比较大。再用跳转表的话会浪费空间;

按位索引

多个分支连续且量比较少的情况,只能精确到分支的范围,后续还需要if:else 精确匹配;

mask = 0b1111  // 对应 case 10,11,12,13
offset = op - 10
if ((mask & (1 << offset)) != 0) {
    // 命中
} else {
    // default
}

线性比较

分支很少,1~3个左右。

总是,如果是switch:case 配合上static/inline 函数,效率的问题是不需要担心的(执行效率上应该仅次于JIT吧)。像QuickJS 就是这么处理的。

上下文与多线程

在一个进程中,函数是可以被多个线程共享的。只要不存在共享的变量,同时执行也没问题。

多线程问题的关键就在于上下文,这也是面向对象编程的核心:如何管理好函数的上下文

虚拟机指令解析

人类比较喜欢包,机器则更喜欢流。一般虚拟机指令和参数都不会太复杂,可以预先定义一些结构体用于匹配指令后的参数。其实有点类似于语法制导的解释器了。
许多状态变量的维护任务最终核心还是给到了上下文。