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