mterp(Dalvik VM)如何组织其字节码解释器循环?

6

我正在学习Android Dalvik虚拟机,当我阅读vm/mterp/out/InterpC-portable.cpp文件中的mterp代码时遇到了一个问题。实际上,这是Dalvik虚拟机的主解释器循环,用于解释dex文件中的字节码。如果我编写这个文件,我会选择switch-case结构来完成以下操作:

while (hasMoreIns()) {
   int ins = getNextIns();
   switch(ins) {
     case MOV:
       //interprete this instruction
       ...
       break;
     case ADD:
       ...
       break;
     ...
     default: break;
   }
 }

然而,mterp使用的方式与我的想法非常不同,它使用了一些我认为是神奇的代码,例如:

FINISH(0);

HANDLE_OPCODE(OP_NOP)
   FINISH(1);
OP_END

HANDLE_OPCODE(OP_MOVE)
   ...
OP_END

...

我谷歌了一下,发现它似乎是一种修改过的“线程化”执行方式,与switch-case风格不同,因为它在while循环中删除了分支操作,所以性能更好。但我仍然无法理解这段代码以及为什么它在性能方面更好。它是如何找到下一个要解释的代码的呢?

1个回答

7
作为简要指导,out目录中填充的是预处理文件,如果您试图查找代码,则不是我所谓的好东西。与InterpC-portable.cpp相对应的源(本身)是portablec目录的内容。
就代码如何执行操作码分派而言,您需要查看portable/stubdefs.cppFINISH宏的定义。
# define FINISH(_offset) {                                                  \
        ADJUST_PC(_offset);                                                 \
        inst = FETCH(0);                                                    \
        if (self->interpBreak.ctl.subMode) {                                \
            dvmCheckBefore(pc, fp, self);                                   \
        }                                                                   \
        goto *handlerTable[INST_INST(inst)];                                \
    }

这个宏用于每个操作码定义的结尾,相当于一个switch (opcode)语句。简单来说,它读取被PC指向的指令代码单元 - inst = FETCH(0) - 从中抓取操作码 - INST_INST(inst) - 并将该操作码作为索引进入所有操作码地址表。使用goto语句直接跳转到地址。

goto是一种“计算跳转”,是非标准的GCC扩展。您可以在GCC手册中了解相关知识,也可以在我2008年在Google IO上关于Dalvik内部的演示中找到一些有关此主题的内容(请访问https://sites.google.com/site/io/dalvik-vm-internals)。

我的演讲还涉及到这种技术的性能特征。简单地说,它节省了一些分支并且与分支预测相对友好。但是,有更好的方法来编写解释器(如我在演讲中介绍的那样,并且Dalvik针对具体CPU的解析器实际上也是这样工作的)。

再补充一些更大背景的信息:将字节码编译为本地CPU指令通常会比最精细调整的解释器执行更快,假设您有足够的RAM来保存编译结果。在Froyo中引入的基于跟踪的Dalvik JIT旨在进行权衡,其中使用适量的额外RAM以实现相当富有成效的性能提升。


谢谢。这个演示是这个链接吗:link - Jin Chen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接