Pages 1 / Total 5 1
已被查看652次    只看楼主
Java虚拟机会比硬件快吗?
主题
xiasug  




等级:终级天王
积分:11516
经验:4202
魅力:7314
威望:0
财富:11037
金钱:0.00元
帖子:657
注册:2007-04-11 20:55
楼主 资料 短消息
Java虚拟机会比硬件快吗? 2007-08-06 10:52
我决定从所进行的bug确定追踪后休息一会儿,我从这些东西之中很难找到乐趣。过一会我将恢复bug确定的讨论。今天,我想要阐明一个通用的误解,那就是java的处理机比动态适应编译器/及时编译器(例如,JIT)要快。我将会通过一些分析来向你们证明我的观点。这些分析将会基于CDC的phoneME的增强虚拟机为例子,但是这个论证当然也可以延伸到其余的虚拟机。让我们开始进入正题吧。
一、硬件加速
   硬件加速是一项技术,它根据速度获得了更好的软件速度。这个方法在图形、声音和数字信号处理方面都取得了很大的成功。在这些情况下,硬件加速将图形、声音和数字信息处理影响了核心的处理器和将主要的一些CPU闲置出来做另外的操作。这个并行化是我们因为硬件提速而提高性能的一个原因。
  硬件加速能提供了特殊的使用说明,这些使用说明能像传统的软件那样起作用,这是第二个原因。当然,特殊的使用说明对算法类型(例如:图形、声音和数字信号处理)来说是如此的特别。因此,如果你的应用程序不能做很多的图形、声音和/或数字信号处理,那么这种硬件加速不会使得你的应用程序运行得更快。
  
     鉴于硬件加速在它们各自应用程序中的显著的的成功,我们开始归纳这些成功,认为所有的硬件加速将会打败软件的解决方案。在Java硬件处理器与JIT比较的情况下,这个归纳显然是不正确的。
二、Java处理器
Java虚拟机规范有它自己的一套说明集合。这些说明中的某一些看起来很像那些将会找到一个典型的CPU指令转码的Java虚拟机指令集合,它将会Java代码执行的性能,这个观察是有根有据的。但是,这种误解是硬件加速也会与虚拟机JIT匹配带来更好的性能。在JIT编译情况下,一个Java处理器将会打败JIT的性能。注意:在接下来的讨论中,我将会简化硬件Java处理器为JPU。
声明:我不会对市场上的任何的硬件和Java处理器实现发表任何评论,而仅仅是从理论的观点来看待问题。
好了,让我们来看一个简单而又特别的例子。




-------------------------------------------------------------------------------------------------------------
┣看┫┣帖┫┣回┫┣复┫┣是┫┣美┫┣德┫┣大┫┣家┫┣记┫┣住┫┣啊┫
图书出版 封面署名 圆您出书梦想 qq:305031557 支持原创 it在中国 网络在中国

IP:123.*.*.*     顶部
回复
xiasug  




等级:终级天王
积分:11516
经验:4202
魅力:7314
威望:0
财富:11037
金钱:0.00元
帖子:657
注册:2007-04-11 20:55
2007-08-06 10:54

三、一个简单的例子
            
这里有一段用Java编程语言写的一小段程序段:
a = a + b; // 将两个整数相加.
当编译成字节码的时候,程序段将会变成如下这样:
      iload_0     //蒋a放入堆栈中.
      iload_1     // 将b放入堆栈中.
      iadd        //弹出这两个值,并将两者相加,
                  //将得出的结果推入堆栈
    istore_0     //蒋结果保存到a中.
    虚拟机的解释器
这儿说明了虚拟机解释器是如何执行上述的代码的:
    // opc_iload_0:
    ldr   r4, [fp, #-140]        //获得本地变量起始的地址locals
    ldrb r3, [r8, #1]!           //计算第一段
    ldr   r2, [r4, #0]            //加载本地的0
    ldr   r1, [r9, r3, asl #2] //计算第二段
    str   r2, [rJSP], #4        //将值放入堆栈中
    mov   pc, r1                  //分出下一个段
    ...
 
    // opc_iadd:
    sub   r4, rJSP, #8           //计算变量的堆栈位置
    ldmia r4, {r2, r4}           //加载变量,例如.从内存中加载两个字的空间from memory
    add   r2, r2, r4              //执行加操作
    ldr   r1, [r9, r3, asl #2] //计算下一个段
    str   r2, [rJSP, #-8]       //存储结果到堆栈中、
    sub   rJSP, rJSP, #4         //调整堆栈指针
    mov   pc, r1                   //分出下一个段
    ...
 
    // opc_istore_0:
    ldrb r3, [r8, #1]!          //计算下一个段
    ldr   r2, [rJSP, #-4]!       //从堆栈中弹出值
    ldr   r4, [fp, #-140]        //获得本地起始的地址
    ldr   r1, [r9, r3, asl #2] //计算一个个部分
    str   r2, [r4, #0]           //将值放入本地的0
    mov   pc, r1                  //分出下一个段
    ...



-------------------------------------------------------------------------------------------------------------
┣看┫┣帖┫┣回┫┣复┫┣是┫┣美┫┣德┫┣大┫┣家┫┣记┫┣住┫┣啊┫
图书出版 封面署名 圆您出书梦想 qq:305031557 支持原创 it在中国 网络在中国

IP:123.*.*.*     顶部
xiasug  




等级:终级天王
积分:11516
经验:4202
魅力:7314
威望:0
财富:11037
金钱:0.00元
帖子:657
注册:2007-04-11 20:55
2007-08-06 10:57
如上所述通过编译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
对于JITa = 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框架指针。



-------------------------------------------------------------------------------------------------------------
┣看┫┣帖┫┣回┫┣复┫┣是┫┣美┫┣德┫┣大┫┣家┫┣记┫┣住┫┣啊┫
图书出版 封面署名 圆您出书梦想 qq:305031557 支持原创 it在中国 网络在中国

IP:123.*.*.*     顶部
xiasug  




等级:终级天王
积分:11516
经验:4202
魅力:7314
威望:0
财富:11037
金钱:0.00元
帖子:657
注册:2007-04-11 20:55
2007-08-06 10:57
实际点讲,最后的存储指令可能需要排除,这取决于字节码是否在它之后。因此,在某些时候,本地变量01的值可能已经在注册器中存在了,这取决于字节码是如何执行这些代码段的。在这种情况下,这2个载入指令可能也需要除去。因此,在理想的JIT条件下,a = a + bJIT编译成一个单独的ARM相加指令。但是在JPU情况下做一个保守的估计,我将会包括我们比较的载入和存储指令。因此,JIT对应的数量如下所示:
指令数量: 4条指令
                            
内存访问的数量: 3次内存访问

机器周期数量 (无内存丢失的情况):  1 + 3*1 = 4个机器周期
                            
机器周期数量(有内存丢失的情况): 1 + 3*4 = 13 个机器周期
   JIT下的理想情况下,我们仅仅将其编译成一条ARM相加指令。
指令数量: 1条指令
                            
内存访问的数量: 0次内存访问

机器周期数量 (无内存丢失的情况):  1个机器周期
                            
机器周期数量(有内存丢失的情况): 1个机器周期
数量的比较
下面是两者耗时度的比较:
机器周期数
解释器
JPU
JIT
理想的JIT
无内存丢失的情况
26
10 (2.60x)
4 (6.50x)
1 (26.0x)
有内存丢失的情况
83
37 (2.24x)
13 (6.38x)
1 (83.0x)
  



-------------------------------------------------------------------------------------------------------------
┣看┫┣帖┫┣回┫┣复┫┣是┫┣美┫┣德┫┣大┫┣家┫┣记┫┣住┫┣啊┫
图书出版 封面署名 圆您出书梦想 qq:305031557 支持原创 it在中国 网络在中国

IP:123.*.*.*     顶部
xiasug  




等级:终级天王
积分:11516
经验:4202
魅力:7314
威望:0
财富:11037
金钱:0.00元
帖子:657
注册:2007-04-11 20:55
2007-08-06 10:58
   表格中的x指明对解释器的提速率。基于这个试验例子,JIT在表现上明显强于JPU。原因在于JIT能够除去某些或全部内存访问(通过保存工作数据在注册器中)处理时间。对比来说,JPU受虚拟机说明的堆栈机器架构(它招致了很多的内存访问)的约束。
   但是在你为这些数字变得疯狂之前,让我们给你降降温。在真实世界中,你可能看不到理论上的JIT在如上环境中的26x83x的加速。这是因为JIT并不总是能够保持所有的工作数据都存储在寄存器中,因为这里只有有限的寄存器集合。这个限制被称为寄存器压力。这在某些时候确实是存在的,但是并不是在所有的情况下,因此,JIT时间的真实数据更接近于JIT在非理想情况下的数据,而不是理想情况下的数据。
   为了对JPU公平一点,围绕堆栈架构问题,一个JPU将可能实现一个堆栈缓存,这在一些注册器将会映像在一些高层的N值的Java操作栈上。它也存在一些本地的注册器映像。这点有效的允许JPUJIT的理想情况一样避免内存访问,因此理论上允许它获得对JIT理想情况下的内存访问速度。但是,注册器压力也像JIT一样限制了JPU
   另外,JPU仅仅能够映像栈中的顶部的大部分操作数,并且可能是最先的少数本地变量。在注册器中的堆栈操作数和/或本地变量获得映像,这通常在运行过程中是不可配置的。但是JIT能够动态的处理这种情况,并且能够任意的选择哪一个参数和/或本地变量能够保存在注册器中。因此,JIT相对JPU来说,具有降低内存访问率的更高的可能性。



-------------------------------------------------------------------------------------------------------------
┣看┫┣帖┫┣回┫┣复┫┣是┫┣美┫┣德┫┣大┫┣家┫┣记┫┣住┫┣啊┫
图书出版 封面署名 圆您出书梦想 qq:305031557 支持原创 it在中国 网络在中国

IP:123.*.*.*     顶部
论坛交流 ›› Java ›› Java虚拟机会比硬件快吗?