threaded code和指令预测

本文探讨了解释字节码执行中的效率问题,包括通过字节码分组、使用哈希表、threaded code技术及指令预测等方法减少switch结构带来的性能损耗。
在解释字节码执行的虚拟机中,一般都会有上一篇说的switch结构,而字节码的类型显然不止前面列出的那些,一个很简单的语言都可能有上百个字节码,于是引入了另一个问题,假设用C或C++实现,在很多编译器中,switch实际会被编译成一连串的if...else if... ... ...else,执行的时候,每条指令都从头开始判断,执行某些指令时,需要成百甚至上千的比较次数,严重影响运行效率。当然这个问题并非是虚拟机解释器独有,而是switch在特定编译器的问题,比方说我们写一个网络处理程序,根据协议号分发处理,如果协议号很多,也是一样的情况。但是这个问题在解释字节码的虚拟机上特别显著,一方面因为字节码很多,另一方面因为每个字节码的执行是很简单的,相对的大量操作就浪费在了这个判断上 

2012年我做了个很简单的语言,解释字节码执行,就碰到了这个问题,尽管字节码并不多,但对效率影响比较明显,当时想了一个笨办法: 
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%以上的情况下都预测成功,提速会非常明显 

显然我们需要一张指令前后关系的统计表,使用上面的统计方法即可,某些指令可能有多条后继指令,可以做多次预测,但预测数量太多反而会造成性能下降,需要一个权衡
C语言-光伏MPPT算法:电导增量法扰动观察法+自动全局搜索Plecs最大功率跟踪算法仿真内容概要:本文档主要介绍了一种基于C语言实现的光伏最大功率点跟踪(MPPT)算法,结合电导增量法与扰动观察法,并引入自动全局搜索策略,利用Plecs仿真工具对算法进行建模与仿真验证。文档重点阐述了两种经典MPPT算法的原理、优缺点及其在不同光照温度条件下的动态响应特性,同时提出一种改进的复合控制策略以提升系统在复杂环境下的跟踪精度与稳定性。通过仿真结果对比分析,验证了所提方法在快速性准确性方面的优势,适用于光伏发电系统的高效能量转换控制。; 适合人群:具备一定C语言编程基础电力电子知识背景,从事光伏系统开发、嵌入式控制或新能源技术研发的工程师及高校研究人员;工作年限1-3年的初级至中级研发人员尤为适合。; 使用场景及目标:①掌握电导增量法与扰动观察法在实际光伏系统中的实现机制与切换逻辑;②学习如何在Plecs中搭建MPPT控制系统仿真模型;③实现自动全局搜索以避免传统算法陷入局部峰值问题,提升复杂工况下的最大功率追踪效率;④为光伏逆变器或太阳能充电控制器的算法开发提供技术参考与实现范例。; 阅读建议:建议读者结合文中提供的C语言算法逻辑与Plecs仿真模型同步学习,重点关注算法判断条件、步长调节策略及仿真参数设置。在理解基本原理的基础上,可通过修改光照强度、温度变化曲线等外部扰动因素,进一步测试算法鲁棒性,并尝试将其移植到实际嵌入式平台进行实验验证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值