JVM指令的基于堆栈的体系结构的优点

gpu*_*guy 7 java

为什么Java虚拟机的设计没有寄存器来保存中间数据值?相反,每件事都在堆栈上工作.有一个基于堆栈的架构而不是注册的任何特定优势?

Pét*_*rök 6

Java旨在从头开始实现可移植性.但是,如果它依赖于您运行它的平台上存在的某些寄存器,那么如何保持字节码可移植?特别是考虑到最初它打算(也)在机顶盒上运行,机顶盒与主流PC有着截然不同的处理器架构.

JVM实际上只知道可用的寄存器和其他硬件特定的东西.然后,JIT编译器可以(并将会)根据需要对这些进行优化.

  • 严格地说,虚拟寄存器不必映射到物理寄存器.从另一个POV,堆栈的硬件支持再次无法保证,对吧? (7认同)
  • 例如,ActionScript VM是基于寄存器的.我认为这种设计的基本原理并不那么简单,最终归结为一系列权衡,Oak设计师(我猜他们 - 在Sun收购之前)是根据他们对语言应用的具体愿景而做出的. (2认同)

cod*_*eim 6

我必须恭敬地不同意以前的答案.

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

EJP说"如果主机有一个堆栈,就像它们一样,没有什么可做的 ".这是一个错误的陈述.建议所有机器都具有能够执行计算的堆栈,这表明堆栈机器究竟是什么的混乱.

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

因此,将一个机器架构的操作码映射到另一个机器架构当然不仅仅是"无关紧要".除非您使用本机Java操作码加速芯片,否则最好不要这么做.

我不认为堆栈机器是便携性的好选择.我说有一些"做什么"来映射堆栈到寄存器寄存器到堆栈的指令.两者都有可能.

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

除非我们为一个非常特定的平台设计一个VM(比如我们已经被要求设计一个只能在GreenSpaces处理器上运行的JVM,我看不到这种情况),所以我假设上下文是一般的应用程序,嵌入式应用,机顶盒,固件控制器,玩具,甚至智能卡.对于所有这些,我看到像ARM这样的8-32位处理器,无论是使用还是可用.主流JVM用C语言编写.C使得设计具有虚拟堆栈或虚拟寄存器操作码的解释器成为可能.在90年代,人们对​​基于堆栈的Java处理器进行了大量讨论,这些处理器直接支持JVM操作码.在2014年,我们在基于寄存器的硬件上看到了这些实现,或者作为ARM926EJ-S上的Jazelle(Java加速)等补充指令,其中还有寄存器和对ARM指令集的支持.

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

push a
push b
add
Run Code Online (Sandbox Code Playgroud)

到本机注册码:

mov eax, [addra]
mov ebx, [addrb]
add eax, ebx
Run Code Online (Sandbox Code Playgroud)

其次,无论操作码流是堆栈还是寄存器,JIT都可以将其编译为本机代码.因此,VM模型的选择仅仅是一种软件模型.注册机器也是虚拟的.它们不编码任何本机寄存器信息,操作码中的寄存器是虚拟符号.

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

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

在VM中,寄存器字节码通常使用大的虚拟寄存器组,需要更大的字节码.在大多数实际硬件上,寄存器通常使用大约3位(Intel x86)到5位(Sparc)进行编码,因此密度可能因VM而异,从CPU到CPU或CPU到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%的优化.调整内嵌核心的任何浪费的努力都可以被视为过早优化.另一方面,如果您没有计划JITter,或者由于内存限制而导致JIT不切实际,那么请优化远程虚拟内核,或者在芯片上实现VM.

  • 如果我能够投票支持这一点,我会立刻获得+50票.从建立之日起对背景架构设置的深入了解就是很棒的阅读.虽然,我无法理解其中的几行,但该帖子必须阅读内容.感谢@mrjoltcola提供这些有价值的信息,并恭敬地批评错误的部分! (2认同)
  • @EJP - 根据要求,我有更新,以明确说明我对你自己的答案不同意.至于"光顾那些已经完成它的人的工作",我已经实现了3个虚拟机从头开始,其中两个是开源的,可供下载,其中一个由O'Reilly和其他几本书覆盖.出版商.我还在Intel x86,Sparc,Motorola,PowerPC,PA-RISC,ARM,MIPS和8051 CPU上研究,编程或编写了JIT,因此我对处理器有一个大概的了解.我并没有声称我做了很棒的事情.但我几乎没有资格成为没有做过的人. (2认同)