为什么CLR和JVM使用基于堆栈的架构?

Cur*_*ous 5 clr

我一直很好奇为什么JVM和CLR有一个基于堆栈的架构?他们为什么不使用基于寄存器的方法?它比基于寄存器的方法有什么好处?

cod*_*eim 8

我曾经考虑过寄存器和堆栈机器之间的差异,并比较指令序列,并运行基准测试......

然后我花了几年时间在Parrot VM上实现这两种类型的机器,这是一台注册机器.我们天真地开始使用固定寄存器集,结合数据和寄存器堆栈,但最终得出结论认为这是一个人为限制,所以我们改为无限寄存器集和分配器.在某些时候,Parrot快速核心(GCC计算goto)优于Mono和JVM解释器核心(非JIT),但差异归结为JIT.Parrot的JIT从未与其他人的质量相匹配.JITter的质量是最终的机器,而这通常是人们所关心的.如果所有VM都使用相同的规则(即,他们有一个约束条件在没有JIT的解释模式下运行),那么我的证据表明注册机器在等效的堆栈机器上具有性能优势.更大的指令,但更少的指令==更高的吞吐量(IPC),以及更好的缓存局部性参考.Dalvik JVM实际上支持我的发现,Dalvik几年没有JIT,并且与其解释器核心竞争.

很少有主流虚拟机专门在解释模式下运行(AFAIK),他们JIT编译,这就是我们的基准测试.解释器核心的要点是在平台上建立存在,进行字节码验证,并在没有JIT的情况下提供故障安全执行核心.当然,这不是一个规则; 有数十亿设备在没有JIT的情况下运行ARM加速JVM,但是在没有内存或CPU限制的情况下,这适用.

我工作并努力调整核心,测试和调优,但最终发现我们真的想要一个快速的JIT.我得出的结论是,如果你最终要进行JIT,那么无论你是实现堆栈还是注册机器来启动,做你喜欢的事情都没关系; 但是使用堆叠机器可以更快地"上市".为虚拟机核心的字节码解释做很多伪寄存器机器虚拟优化部分是一种浪费,因为它不是真正的原生优化.软核不像真实处理器那样进行分支预测,寄存器重命名,指令重新排序,并行执行或预取.我的感觉是,一旦我们有一个高质量的JIT到原生二进制,我们到达同一个目的地.

出于这些原因,我在技术上倾向于基于堆栈的机器:

  1. 简单 - 维护代码少得多=错误少
  2. 是时候实施了

但在视觉和情感方面,我更喜欢注册机:

  1. 视觉概念模型更贴近机器和我的大脑
  2. 灵活性 - 编译器可以使用SSA以不同的顺序评估其表达式树.

注意我没有说编译器可以更"轻松"生成代码.这似乎是那些主要使用堆栈机器的人喜欢争辩的人.我不相信,也没有发现那是真的.我看到很多爱好编译器在短时间内写在Parrot和CLR上,虽然我承认CLR上的那些具有更高的质量,但这主要是生态系统和可用工具的质量之一.我自己在两个平台上编写了编译器,发现存在权衡,但还不足以让人失眠.

这是一个受过教育的猜测,因为我的现实世界的经验不包括写一个完整的抖动,所以我没有第一手的经验比较JITting各种形式的操作码的优点和缺点,但我的看法是,如果你打算包括一个JIT,然后创建一个非常复杂的虚拟机操作码核心相当于过早优化.你在其他地方的时间更好.


usr*_*usr 3

仅仅链接到一篇文章通常是不合适的,但这次我将破例:Eric Lippert 的这篇文章正好回答了这个问题。