如上所述通过编译CVM的解释器循环运用gcc进入ARM。我挑选ARM仅仅作为指令集的一个例子。我的目的是获得一个真实世界的例子,这个例子能够做一些耗时的工作。但是,我把一些注册名用符号等价物来表示,使得集合的代码更加容易理解。JSP是包含Java堆栈指针的一个注册器。
在如上情况中,我并没有说明iload_1字节码的解释器段,因为它本质上与iload_0字节码的解释器段是相同的,除了一个不同的对段指针的偏移量外。
在如上代码中,我也指出了一些指令是用来计算下一个段的。我的意思是这个解释器段的指令地址用来执行下一个字节码是有效的。因此,注释“分出下一个段”实际上的意思是“跳到下一个字节码”。
因此,为了执行上面的a = a + b语句,使用了虚拟机解释器。
指令的数量:
6 (iload_0) + 6 (iload_1) + 7 (iadd) + 6 (istore_0) = 25 条指令
内存访问数量:
5 (iload_0) + 5 (iload_1) + 4 (iadd) + 5 (istore_0) = 19 次内存访问量
对于内存访问,我仅仅计算了加载和存储的指令,而没有计算转向语句。
基于加强的ARM 1110处理器,在最佳的条件下(所有的内存访问都是在缓存中准备),每一条指令将会至少执行一个机器循环(如果我没记错的话)。如果有一个错过了缓存,那么它的内存访问将会占据4个机器周期。另外,它将占用更多的时间。注意:Idmia指令占用两个周期的原因是因为它要载入两次。因此,在理想的情况下:
机器周期个数
1 + 5*1 (iload_0) + 1 + 5*1 (iload_1) + 4 + 4*1 (iadd) + 1 + 5*1 (istore_0) = 26 个机器周期
作为对比,让我们看看所有的内存访问会占用4个机器周期来完成的情况。这是最不理想的情况:
机器周期个数(有缓存丢失的情况):
1 + 5*4 (iload_0) + 1 + 5*4 (iload_1) + 4 + 4*4 (iadd) + 1 + 5*4 (istore_0) = 83个机器周期
Java硬件处理器
作为硬件处理器,指令作为它们自己的字节码执行。但是,执行它们的机器周期个数能基于解释器来估计。
对于一个JPU,它没有任何解释器。因此,这里没有任何的“下一段”,“下一段”变成了下一个字节码指令,它被JPU的程序计数器注册器所指向。
我也假定它是一个假定的注册器,它指向了当前的Java栈的起点。有了它,获得本地变量的地址的步骤能够被排除。
留下来的必要操作是JPU仍然必须执行(即使它在场景后面)。在如上的解释器段中,这个必要的操作是表示asm代码的行数。JPU的指令数如下:
“ASM指令”的数量:
2 (iload_0) + 2 (iload_1) + 3 (iadd) + 2 (istore_0) = 9 条指令
内存访问数量:
2 (iload_0) + 2 (iload_1) + 3 (iadd) + 2 (istore_0) = 9次内存访问
机器周期个数 (没有内存丢失的情况):
2*1 (iload_0) + 2*1 (iload_1) + 1 + 3*1 (iadd) + 2*1 (istore_0) = 10个机器周期
机器周期个数(有内存丢失的情况):
2*4 (iload_0) + 2*4 (iload_1) + 1 + 3*4 (iadd) + 2*4 (istore_0) = 37个机器周期
注意:JPU不能在执行字节码之前将其翻译成等价的ARM指令。但是,从内部来看,JPU仍然需要做一些等量的工作。我将会使用ARM指令作为一个被JPU硬件工作的一个近似值。
另外还要注意,Idmia指令与两个load指令等价。如果你想要核对如上的数据要记住这一点。
JIT
对于JIT,a = a + b语句将会编译成如下的ARM指令:
ldr r7, [rJFP, #-140] //加载本地变量0
ldr r8, [rJFP, #-136] //加载本地变量1
add r7, r7, r8 //将两者相加
str r7, [rJFP, #-140] //将计算结果存储到本地变量0中
rJFP是一个注册器,它被JIT用来包含Java框架指针。