【问题标题】:How does mterp (Dalvik VM) organize its byte-code interprete loop?mterp (Dalvik VM) 如何组织其字节码解释循环?
【发布时间】:2026-02-05 12:35:01
【问题描述】:

我正在研究 Android Dalvik VM,当我阅读文件 vm/mterp/out/InterpC-portable.cpp 中的 mterp 代码时遇到一个问题。实际上它是dalvik vm的主要解释器循环来解释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 循环中的分支操作。但我仍然无法理解这段代码以及为什么它的性能更好。它如何找到下一个要解释的代码?

【问题讨论】:

    标签: android performance interpreter


    【解决方案1】:

    作为一个简短的指导,out 目录充满了预处理文件,如果您想弄清楚代码,我认为这不是一件值得阅读的事情。对应于InterpC-portable.cpp 的源(本身)是portablec 目录的内容。

    关于代码如何分派操作码,您需要查看FINISH 宏的定义,在portable/stubdefs.cpp

    # 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 是一个“计算的 goto”,它是一个非标准的 GCC 扩展。您可以在 GCC 手册中阅读有关它的内容,也可以在我 2008 年在 Google IO 上关于 Dalvik 内部的演示文稿中找到有关该主题的一些信息。(在 https://sites.google.com/site/io/dalvik-vm-internals 找到它。)

    我的演讲还谈到了这种技术的性能特点。简而言之,它节省了一些分支,并且在分支预测方面表现相对较好。但是,还有更好的方法来编写解释器(正如我在演讲中介绍的那样,并且 CPU 特定的 Dalvik 解释器实际上可以工作)。

    对于更大的上下文,如果您有足够的 RAM 来保存编译结果,那么将字节码编译为本机 CPU 指令通常会比经过优化的解释器更快地执行。 Froyo 中引入的基于跟踪的 Dalvik JIT 旨在进行权衡,其中使用适量的额外 RAM 来实现相当丰硕的性能提升。

    【讨论】:

    • 谢谢。演示文稿是:link 吗?