谈谈中间代码

   1。中间代码的每个方法在第一次运行时必然要编译为本地代码,这一步中间代码一定比本地代码多花时间。所以问题的关键转换为这一编译过程要花多少时间,JAVA我不清楚,不过.NET的表现还是非常好的。JIT编译器将中间代码编译为本地代码,这个过程的复杂度远远小于传统的编译器,因为很多相当耗时且困难的部分已经在“源代码-中间代码”阶段做完了,当你剖析一下CLR编译器编译出来的IL中间代码,你会发现这种基于堆栈的语言其实很容易翻译为本地代码。从IL代码编译为本地代码,在算法上最重要的一步就是运行一个有限状态机体系:分析组成IL代码的字节流中的每一个Byte值,然后放到不同的分支处理结构中去处理,从而输出表示本地代码的字节流。其实以现在的硬件效能,这一步的时间倒真的不是问题。况且每个方法只会编译一次,而一个方法几乎都要被多次使用,平均算下来,则就显得微不足道了。


    2。JIT编译器会根据不同的硬件平台优化编译出不同的代码,而不需多写任何代码。这一点是本地代码所不可比的。直观的看个例子:

        static void Main(string[] args)
        {
            
float f1 = 324.6f;
            
int i1 = (int)f1;

            
float f2 = 9.0f;
            
double d1 = (double)f2;

            
double d2 = 3445.67;
            
long l1 = (long)d2;
        }


    
    再看两个硬件平台编译的结果:第一个是支持X64指令集的64位处理器(64位Vista),第二个是普通的支持SSE2的32位处理器(虚拟机中的)(手头上没有更差的了,谁有P3、K7之类的可以编译试试)。

       //面向X64平台 

       
static void Main(string[] args)
        {
00000000  mov         qword ptr [rsp+8],rcx 
00000005  sub         rsp,58h 
00000009  xorps       xmm0,xmm0 
0000000c  movss       dword ptr [rsp
+20h],xmm0 
00000012  mov         dword ptr [rsp+24h],0 
0000001a  xorps       xmm0,xmm0 
0000001d  movss       dword ptr [rsp
+28h],xmm0 
00000023  xorpd       xmm0,xmm0 
00000027  movsd       mmword ptr [rsp+30h],xmm0 
0000002d  xorpd       xmm0,xmm0 
00000031  movsd       mmword ptr [rsp+38h],xmm0 
00000037  mov         qword ptr [rsp+40h],0 
00000040  mov         rax,642801A2188h 
0000004a  mov         eax,dword ptr [rax] 
0000004c  test        eax,eax 
0000004e  je          
0000000000000055 
00000050  call        FFFFFFFFFF7AB2D0 
00000055  nop              
            
float f1 = 324.6f;
00000056  movss       xmm0,dword ptr [000000E0h] 
0000005e  movss       dword ptr [rsp
+20h],xmm0 
            
int i1 = (int)f1;
00000064  cvttss2si   eax,dword ptr [rsp+20h] 
0000006a  mov         dword ptr [rsp
+24h],eax 

            
float f2 = 9.0f;
0000006e  movss       xmm0,dword ptr [000000E8h] 
00000076  movss       dword ptr [rsp+28h],xmm0 
            
double d1 = (double)f2;
0000007c  cvtss2sd    xmm0,dword ptr [rsp
+28h] 
00000082  movsd       mmword ptr [rsp+30h],xmm0 

            
double d2 = 3445.67;
00000088  movsd       xmm0,mmword ptr [000000F0h] 
00000090  movsd       mmword ptr [rsp+38h],xmm0 
            
long l1 = (long)d2;
00000096  cvttsd2si   rax,mmword ptr [rsp+38h] 
0000009d  mov         qword ptr [rsp
+40h],rax 
        }
000000a2  jmp         00000000000000A4 
000000a4  add         rsp,58h 
000000a8  rep ret          


        //IA32 with SSE2

        
static void Main(string[] args)
        {
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        edi  
00000004  push        esi  
00000005  push        ebx  
00000006  sub         esp,5Ch 
00000009  xor         eax,eax 
0000000b  mov         dword ptr [ebp
-10h],eax 
0000000e  xor         eax,eax 
00000010  mov         dword ptr [ebp-1Ch],eax 
00000013  mov         dword ptr [ebp-3Ch],ecx 
00000016  cmp         dword ptr ds:[001F9150h],0 
0000001d  je          
00000024 
0000001f  call        79C86E7F 
00000024  fldz             
00000026  fstp        dword ptr [ebp-40h] 
00000029  xor         ebx,ebx 
0000002b  fldz             
0000002d  fstp        dword ptr [ebp
-48h] 
00000030  fldz             
00000032  fstp        qword ptr [ebp-50h] 
00000035  fldz             
00000037  fstp        qword ptr [ebp-58h] 
0000003a  xor         esi,esi 
0000003c  xor         edi,edi 
0000003e  nop              
            
float f1 = 324.6f;
0000003f  mov         dword ptr [ebp
-40h],43A24CCDh 
            
int i1 = (int)f1;
00000046  fld         dword ptr [ebp-40h] 
00000049  fstp        qword ptr [ebp-68h] 
0000004c  movsd       xmm0,mmword ptr [ebp
-68h] 
00000051  cvttsd2si   eax,xmm0 
00000055  mov         ebx,eax 

            
float f2 = 9.0f;
00000057  mov         dword ptr [ebp-48h],41100000h 
            
double d1 = (double)f2;
0000005e  fld         dword ptr [ebp
-48h] 
00000061  fstp        qword ptr [ebp-50h] 

            
double d2 = 3445.67;
00000064  fld         qword ptr ds:[004A1558h] 
0000006a  fstp        qword ptr [ebp
-58h] 
            
long l1 = (long)d2;
0000006d  fld         qword ptr [ebp
-58h] 
00000070  sub         esp,8 
00000073  fstp        qword ptr [esp] 
00000076  call        79A52B3F 
0000007b  mov         esi,eax 
0000007d  mov         edi,edx 
        }
0000007f  nop              
00000080  lea         esp,[ebp-0Ch] 
00000083  pop         ebx  
00000084  pop         esi  
00000085  pop         edi  
00000086  pop         ebp  
00000087  ret              

    显然,JIT编译器会针对不同的处理器做不同的处理,如果是64位处理器并且是64位操作系统,JIT会毫不犹豫地使用64位指令集、毫不犹豫地使用多出来的那8个64位通用寄存器,而不需在源代码中修改任何东西(不懂汇编也没关系,就数数两种环境所编译出来的汇编代码量就知道哪个好了(本人其实也不大懂,主要是用不上,没认真研究过)),再看最后一句:long l1 = (long)d2; 一个编译出的结果是直接使用SSE2指令cvttsd2si,另一个则调用了一个方法来完成,效能明显不一样。

    而本地代码却只能按照
最低的平台标准编译。(当然本地代码也可以针对不同的平台发布不同的程序集,只是我觉得这样做所带来的麻烦远比收益大)


    3。不仅跨平台而且跨语言。跨平台是JAVA给我们带来的震撼,而跨语言则是.NET给我们带来震撼(.NET的跨平台体现在Silverlight上)。很多人至今对性能的认知都存在误区,其实面对越来越复杂多变的需求,面对速度差异越来越大的CPU和磁盘IO,面对大量的数据库操作,CPU执行上的那点差异还算什么?跨平台的好处大家都知道,只是很少人能总结到这本质上是一种对软件工程中变化点的封装,使得项目完全不必考虑目标硬件平台,所有变化点都封装在JIT编译器中,性能的优化则是额外附加的好处。而跨语言这样的设计出发点则在于软件复用这个层次,复用的是什么?复用的是各种语言所编写的类库,复用的是精通不同语言的程序员(人力资源)。
原文地址:https://www.cnblogs.com/WYB/p/1229188.html