我试图了解在解码和调度解释和线程解释中执行程序期间的实际差异。
两者的例子都会有帮助。
我了解 Java 字节码的工作原理以及汇编语言的工作原理。但是 DDI 和 TI 适合在哪里呢?
我试图了解在解码和调度解释和线程解释中执行程序期间的实际差异。
两者的例子都会有帮助。
我了解 Java 字节码的工作原理以及汇编语言的工作原理。但是 DDI 和 TI 适合在哪里呢?
(注意:我假设“解码和调度”是指基于开关的解释器。)
在运行时,基于开关的解释器和线程解释器之间的区别基本上是执行的跳转次数。
在基于开关的解释器中,指令在某个中心位置被解码,并且根据解码结果,跳转到处理解码指令的代码片段。一旦那段代码完成了对指令的解释,它就会跳回到集中解码代码,继续执行下一条指令。这意味着(至少)每个解释指令执行两次跳转。以下一段 C 代码说明了这种解释器的外观:
typedef enum {
add, /* ... */
} instruction_t;
void interpret() {
static instruction_t program[] = { add /* ... */ };
instruction_t* pc = program;
int* sp = ...; /* stack pointer */
for (;;) {
switch (*pc++) {
case add:
sp[1] += sp[0];
sp++;
break;
/* ... other instructions */
}
}
}
在线程解释器中,解码代码不是集中式的,而是在处理指令的每段代码的末尾重复。这意味着,一旦指令被解释,解释器不会跳回到某些集中的解码代码,而是解码下一条指令并立即跳转到它。在 ANSI-C 中有效地实现线程代码实际上是不可能的,但是 GCC 的“computed goto”扩展对此非常有效。这是以前解释器的线程版本:
void interpret() {
void* program[] = { &&l_add, /* ... */ };
int* sp = ...;
void** pc = program;
goto **pc; /* jump to first instruction */
l_add:
sp[1] += sp[0];
++sp;
goto **(++pc); /* jump to next instruction */
/* ... other instructions */
}
除了保存跳转之外,这种线程解释器也更有效,因为现代 CPU 可以更好地预测复制的间接跳转(到下一条指令)。Anton Ertl 在他的主页上有一些有趣的论文,特别是一篇名为“高效解释器的结构和性能”的论文,上面的代码片段就是从中改编而来的。