“字节码程序通常通过一次解析一条指令来执行。这种字节码解释器非常便携。一些称为动态翻译器或“即时”(JIT)编译器的系统将字节码翻译成机器语言在运行时根据需要:这使虚拟机不可移植。”
关于这一段的一个问题是:字节码被处理后,解析后的指令和机器语言(或机器码)有什么区别?
“字节码程序通常通过一次解析一条指令来执行。这种字节码解释器非常便携。一些称为动态翻译器或“即时”(JIT)编译器的系统将字节码翻译成机器语言在运行时根据需要:这使虚拟机不可移植。”
关于这一段的一个问题是:字节码被处理后,解析后的指令和机器语言(或机器码)有什么区别?
JIT 不同于字节码解释器。
考虑以下 C 函数:
int sum() {
return 5 + 6;
}
这将直接编译机器码。x86 和 ARM 处理器上的确切指令会有所不同。
如果我们编写一个基本的字节码解释器,它可能看起来像这样:
for(;;) {
switch(*currentInstruction++) {
case OP_PUSHINT:
*stack++ = nextInt(currentInstruction);
break;
case OP_ADD:
--stack;
stack[-1].add(*stack);
break;
case OP_RETURN:
return stack[-1];
}
}
这可以解释以下指令集:
OP_PUSHINT (5)
OP_PUSHINT (6)
OP_ADD
OP_RETURN
如果您在 x86 或 ARM 上编译了字节码解释器,那么您将能够运行相同的字节码,而无需对解释器进行任何进一步的重写。
如果您编写了 JIT 编译器,则需要为每个受支持的处理器发出特定于处理器的指令(机器代码),而字节码解释器依赖于 C++ 编译器来发出特定于处理器的指令。
在字节码解释器中,指令格式通常设计用于使用移位和掩码运算符进行非常快速的“解析”。解释器在“解析”(我更喜欢“解码”)指令后,立即更新虚拟机的状态,然后开始解码下一条指令。因此,在解释器中处理完字节码后,不会有任何残留。
在 JIT 编译器中,字节以大于单条指令的单位进行处理。最小单元是基本块,但现代 JIT 会将较大的路径转换为机器代码。这是一个翻译步骤,翻译步骤的输出是机器码。原始字节码可能保留在内存中,但不用于实现——因此没有真正的区别。(尽管 JIT 虚拟机的机器代码与本机代码编译器发出的机器代码做的事情仍然很典型。)
没有区别 - JIT 编译器正是为此完成的 - 它生成在硬件上执行的机器代码。
最终,这一切都归结为机器指令。
如您所知,使用 #1 时开销最少,而使用 #3 时开销最大。因此,在初始编译开销之后,#1 上的性能应该是最快的,而 #2 上的性能应该也一样快。