switch (code >> 4)
{
case CODE_GROUP_1:
{
switch (code)
{
case CODE_LOAD:
...
}
}
case CODE_GROUP_2:
{
...
}
...
}
就是将字节码分组,然后每条指令至少要两次判断,但是平均判断次数降低了,当然分组组数和每组数量要设计,上面code >> 4只是个例子
其实很多人都知道怎么解决这个问题,用一个hash表即可,比如python做服务器的时候,可能会根据协议到不同的分支:
protocol_process_map = {PROTOCOL_A : process_a,
PROTOCOL_B : process_b,
...}
处理协议的时候,从表中找到函数,直接调用即可,无论多少协议,时间是O(1)
但是,如果在字节码处理上使用这种方式,则每个字节码都会进行一次函数调用,而相对于每个字节码的操作的简单性来说,函数调用还是很费时,于是就引入了threaded code技术,具体方法是,将字节码指令列表用对应的需要跳转的地址替代,然后一个个jmp过去,例(转自wikipedia):
start:
ip = &thread
top:
jump *ip++
thread:
&pushA
&pushB
&add
...
pushA:
*sp++ = A
jump top
pushB:
*sp++ = B
jump top
add:
*sp++ = *--sp + *--sp
jump top
这个处理的字节码是:
pushA
pushB
add
thread是字节码转换成的一个jmp序列,ip是索引,按照thread表里面的地址依次跳到对应位置执行即可
不过,这个技术也就字节码能用,因为字节码是确定的,预先编译(这也是编译,虽然过程简单)成地址表是可行的,但对于一般的switch来说,需要用上述hash表的方式,做成表驱动,假如各case的值范围不大,连续性也比较好,做成一个地址数组用switch条件做索引即可,否则需要生成一个hash表,为效率考虑,hash函数需要尽量简单,冲突也要尽量少,可能会浪费空间来减少冲突
P.S.对于N个不同的数据,放在容量为N的hash表中且无hash冲突,是可能的,这就是perfect-hash,但这需要利用计算机来找一个合适的hash函数,算法比较复杂,而且找出来的函数可能运算比较慢(当然也是O(1)的)
虽然已经有了这个编译技术,但并非所有编译器支持,ruby在1.9版本开始解释字节码执行,就立刻使用了这种技术,1.8之前是解释AST执行,但python从很早就解释字节码实现了,在没有threaded code的情况下,python采用了指令预测的技术来减少switch的消耗
考虑switch的if...else if... ... ...else实现,假设各条件的出现是平均几率随机的,那这显然是一个O(N)的顺序查找过程,但考虑实际情况,字节码的出现几率一般是不平均的,例如栈虚拟机中load操作数量明显大大高于其他,而运算中,加减运算比幂次(如python的**)出现几率也高很多,普通计算程序可能根本不会用到类似异或这种运算(加密算法用的比较多),于是,如果我们把常见的指令放在switch的前面,就能提高效率,一个精心设计过case顺序的虚拟机甚至可能比一个糟糕的顺序效率高20%
某些指令如load,其出现的频繁程度是可想而知的,但并非所有字节码都这么明显,为了达到一个好的顺序,比较好的办法是采用数据挖掘,统计的方法。在有了编译器后,可以写一批代码(常用算法,经典benchmark之类),编译后统计各个指令的频率,即可得到一个较好的顺序,然后可以将虚拟机发布出去给大家使用,这时候至少不会太差,然后收集实际使用中能拿到的代码(当然是合法途径拿到的,不要像某些软件一样偷别人代码),继续统计和优化调整
上面这个办法只是基于单条指令频率的预测,将可能出现的放在前面,python还实现了指令间关联的预测。可以注意到,字节码指令数量明显比源代码语句数量要多很多,这是因为源代码的一条stmt会转成多条字节码,而很多情况下一条指令转成字节码是有固定格式的,比如,我们经常写if的时候,后面跟的是判断表达式(<,>,==之类),而if在算完表达式后下一条就是pop_jmp_if_false,因此:
case CODE_EQUAL: //==运算
{
...//计算过程
//假设idx已经++过了,指向下一条指令
if (inst_list[idx].code == CODE_POP_JMP_IF_FALSE)
{
goto do_pop_jmp_if_false;
}
continue;
}
这个预测在失败的时候会浪费一次判断,但是成功的时候可以节省很多次判断,如果90%以上的情况下都预测成功,提速会非常明显
显然我们需要一张指令前后关系的统计表,使用上面的统计方法即可,某些指令可能有多条后继指令,可以做多次预测,但预测数量太多反而会造成性能下降,需要一个权衡