8

为什么 Java 虚拟机被设计为没有寄存器来保存中间数据值?相反,每件事都在堆栈上工作。拥有基于堆栈的架构而不是寄存器有什么特别的优势吗?

4

3 回答 3

7

我不得不恭敬地不同意以前的答案。

存在表达式堆栈的假设并不比存在寄存器更好。通常注册机不能直接执行栈操作码,栈机也不能直接执行注册操作码。它们必须被映射。

EJP 说“如果主机有一个堆栈,就像他们一样,那就没什么可做的了。这是一个错误的说法。提出所有机器都具有能够执行计算的堆栈显示了对堆栈机器的真正含义的混淆。

  1. 基于堆栈的机器在“表达式堆栈”的顶部有一个带有隐式操作数的指令集。不是通用堆栈。通用堆栈不是堆栈机器制造的。他们可以保存/恢复值。所有平台都有。但他们不能执行计算。
  2. 基于寄存器的机器使用指令流中编码的显式虚拟寄存器的操作数执行典型的操作码。这些虚拟机仍然具有通用堆栈和操作码来保存和恢复寄存器。您不能堆栈计算操作映射到数据堆栈。核心指令集的操作数是寄存器、内存地址或立即数(在操作码中编码)。

因此,将一种机器架构的操作码映射到另一种机器架构肯定不仅仅是“无事可做”。除非您在具有本机 Java 操作码加速的芯片上,否则最好不要假设。

我不认为堆栈机器是可移植性的好选择。我是说有“一些事情要做”来将指令从堆栈映射到寄存器寄存器到堆栈。两者都是可能的。

然而,很多谈话都是学术性的。在实践中,2014年流行的平台是注册。甚至基于堆栈的“处理器”通常也是在寄存器芯片顶部实现的软芯片。请参阅 Java Card 3 规范并查看实现并找出实际使用的处理器。我会把它留作练习。

除非我们正在为一个非常特定的平台设计 VM(例如,我们被要求设计一个只能在 GreenSpaces 处理器上运行的 JVM,但我认为这不会发生),否则我假设上下文是通用应用程序、嵌入式应用程序、机顶盒、固件控制器、玩具,甚至智能卡。对于所有这些,我看到了 8-32 位处理器,如 ARM,无论是使用的还是可用的。主流的 JVM 是用 C 编写的。C 使得设计具有虚拟堆栈或虚拟寄存器操作码的解释器成为可能。在 90 年代,有很多关于直接支持 JVM 操作码的基于堆栈的 Java 处理器的讨论。2014 年,我们在基于寄存器的硬件上看到了这些实现,

对于一般应用程序,基于堆栈的 VM 在实践中肯定不会映射到堆栈。这些机器都使用基于寄存器的主要指令;堆栈操作用于保存和恢复寄存器或调用堆栈,而不是进行计算、逻辑或分支。堆栈虚拟机 JIT 到机器的本机指令集,无论是堆栈还是寄存器指令集。根据Hennessy And Patterson 的“计算机架构一种定量方法”,自 1980 年以来,几乎所有新的处理器架构都已注册。这意味着寄存器-寄存器、寄存器-内存或寄存器立即指令集。在没有基于堆栈的添加的机器上,您不能在堆栈上添加 2 个值。在 x86 上,用于 ADD 操作的基于堆栈的操作码可能转换为:

push a
push b
add

到本机寄存器代码:

mov eax, [addra]
mov ebx, [addrb]
add eax, ebx

其次,无论操作码流是堆栈还是寄存器,JIT 都可以将其编译为本机代码。所以VM模型的选择简单来说就是软件模型。注册机也是虚拟的。它们不编码任何本机寄存器信息,操作码中的寄存器是虚拟符号。

现在在设计 Java 时,考虑的是小操作码和 8 位处理器兼容性。基于堆栈的操作码小于寄存器操作码。所以这是有道理的。我确定我在某处读到,除了简单之外,这是 James Gosling 为 Oak(Java 的原始名称)选择它的主要原因之一。我只是没有参考。在这方面,我同意 Péter Török 的观点。

  • 堆栈操作码有明显的好处。码流通常更小/更密集。关于 JVM 和 CLR,观察到的基于堆栈的字节码可能比其他机器小 15-20%。堆栈字节码可以很容易地以 <= 8 位编码。(Forth 机器可以有 20 个左右的操作码)。opstream 更容易编码/解码。尝试为 Java 然后为 x86 编写汇编程序或反汇编程序。
  • 注册操作码有明显的好处。对表达式进行编码的操作码更少 = 更好的 IPC = 解释器中的高级分支更少。它还可以直接将少量寄存器(8 到 16 个)映射到几乎每个现代处理器。由于更好的缓存引用局部性,使用寄存器可以实现更高的吞吐量。相反,堆栈机器使用大量的内存带宽。

在 VM 中,寄存器字节码通常使用大型虚拟寄存器集,需要更大的字节码。在大多数真实硬件上,寄存器通常以大约 3 位(Intel x86)到 5 位(Sparc)编码,因此密度可能因 VM 到 VM 或 CPU 到 CPU 或 VM 到 CPU 不同。Dalvik 使用 4 到 16 位来表示寄存器,而 Parrot 在所有操作码上使用每个寄存器 8 位(至少我使用的 v2 字节码格式)。Dalvik 实现了更好的平均密度。根据我构建它们的经验,很难在 8 位字节码内构建通用寄存器机器,除非您严格遵守原语并使用小型寄存器文件。这可能看起来不直观,但通常单个操作码实际上具有多个具有不同寄存器类型的编码。

最后一点:当 JIT 出现时,许多软核优化可能会消失。

我认为堆栈机器更好地映射到硬件的论点的主要例外是它忽略了 JVM 运行的位置和/或技术的发展方向。在 Chuck Moore 之外,我不知道有任何人设计基于堆栈的处理器(IGNITE 和 GreenSpaces GA144),并且大多数新开发是寄存器机器。堆栈机器的论点主要是学术性的。对于每个 8 位堆栈处理器参数,我可以向您展示几个带有 C 编译器的寄存器机器(带有寄存器的 Hitachi H8、带有寄存器的 ARM926、Intel 8051)。与 Java 相比,您可能更可能在纯堆栈处理器上使用 Forth 进行编写。对于一个新平台,使用便宜的 ARM 处理器更有意义,其中有 C 编译器、完整的 JVM 等。这些运行寄存器指令集。

那么,如果这是真的呢?有关系吗?根据我的经验,我的观点是“不像人们想象的那样多”。请记住,字节码只是一种中间表示。一台机器的纯解释核心通常是一块垫脚石、一座桥梁、一个默认的故障安全核心。最终目的地是最终的版本 2,它具有对本机性能的 JITter。因此,许多做过一两次的人持有的观点是,保持核心尽可能简单并转向 JIT 是有意义的,在那里将 90% 的优化都花在了那里。调整 interpeted 核心的任何浪费精力都可以被视为过早的优化。另一方面,如果您不计划 JITter,或者由于内存限制而导致 JIT 不切实际,则在虚拟内核上进行优化,或者在芯片上实现 VM。

于 2014-06-19T07:42:52.683 回答
6

Java 从一开始就被设计为可移植的。但是,如果它依赖于运行它的平台上存在的某些寄存器,那么如何保持字节码的可移植性呢?特别是考虑到它最初是打算在机顶盒上运行的,机顶盒的处理器架构与主流 PC 非常不同。

只有运行时 JVM 才真正知道可用的寄存器和其他硬件特定的东西。然后 JIT 编译器可以(并且将)在适用时针对这些进行优化。

于 2012-05-09T11:30:22.990 回答
2

在设计虚拟机时,设置堆栈比设置一组虚拟寄存器要容易得多。如果主机有一个堆栈,就像他们一样,没有什么可做的,如果它有寄存器,你仍然可以将它们用于其他东西:临时等。另一方面,如果主机没有寄存器,只有一个堆栈,并且您围绕寄存器设计您的VM,您手上有一个主要的实现问题,因为您必须使用本机堆栈,其方式会干扰其作为虚拟堆栈的使用。

于 2012-10-28T22:37:38.893 回答