谈谈中间代码
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;
}
{
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
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
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编译器中,性能的优化则是额外附加的好处。而跨语言这样的设计出发点则在于软件复用这个层次,复用的是什么?复用的是各种语言所编写的类库,复用的是精通不同语言的程序员(人力资源)。