五千年(敝帚自珍)

主题:【原创】Java和.NET哪个运行的更快? -- Highway

共:💬24 🌺19
全看分页树展 · 主题
家园 【原创】Java和.NET哪个运行的更快?

对于这个问题,有的人回答得非常干脆:“当然是.NET了”。你如果问他为什么,他会简单的回答:“.NET有JIT(Just-in-time-compiler),运行的是编译好的机器代码,Java是解释执行的,速度当然不能和.NET的Native code相提并论了!”

事实是这样的吗?

Java和.NET到底哪个运行的更快可不是一个简单的问题,你千万不要相信任何一个简单的Benchmark程序的测试结果。因为性能问题涉及的方面太广,很难简单武断的下结论的。比如说要全面比较Java和.NET的性能,你可能要测试整数,浮点数,各种String操作,Collection(standard and generics)性能, XML (SAX and DOM) 操作,文件IO (读,写,顺序,随机,文本,二进制),数据库操作,线程同步(Thread synchronization), Lock机制,网络操作,串行化/反串行化,Memory allocate/release, Garbage collection等等等等。就我做过的一些测试来看,Java和.NET各有千秋,各有擅长的地方,也都有自己的弱点。

今天我在这里想说的是Java和.NET在执行程序时各自的特点,希望你看后能对Java和.NET有个更清晰地认识(或是给我扔几块砖,让我有个更清晰地认识)。

Java的Hotspot引擎

性能是早期Java的一个最大问题。为了这事,SUN和业界的其他公司没少下功夫,也曾经出现了很多方案和提议。比较有趣的建议如开发专门执行Java的协处理器;将Java byte code转化为本地代码;甚至是将Java源程序先转化为C++,然后编译成C++可执行文件等等。那时候,不少公司都开发了自己的Java虚拟机,比如IBM,微软。到后来,随着Java的不断发展和成熟,最后SUN的Hotspot引擎脱颖而出了,成了最广泛使用的Java虚拟机。

点看全图

外链图片需谨慎,可能会被源头改

Hotspot Engine到底是如何工作的呢?

当Java虚拟机拿到byte code的时候,它总是先要解释执行。与此同时,它观察和记录程序的执行特点。随着程序的不断运行,它对程序的行为越来越清楚,它会发现程序中的热点(就是所谓的Hotspot)。这些热点就是频繁被执行,占用CPU最多的程序段。这时候,它就会编译这段程序,将它转化为本地代码(Native code),,由于它在这个时候掌握了程序的执行特点,所以它敢于“大胆”地进行程序优化,比如说最常见的Method inlining。这样一来,瓶颈被突破了,程序运行速度立刻得到提升,在很多类型的运算上,其速度和C++相似,甚至更快。这就是所谓的“动态优化”。

有人说了,Java code怎么可能比C++更快呢?原因很简单,C++进行的优化是静态优化,都是在编译的时候进行的。一旦编译链接生成的可执行本地代码,就盖棺定论了,不能更改了,除非是Hacker或是病毒。就现在的编译技术来看,静态优化在总体上还是最成熟的,并且在编译的时候它没有时间压力,可以花很长时间来优化程序。这点Java和.NET是不允许的。但是静态优化也有它的缺点,因为它不知道这些程序在运行的时候具体会有什么特征,无法针对性地进行优化。比如它就不可能“大胆”的进行Method inlining。因为它胆子大了就可能犯错误。比如一个Class A,它有个简单函数public int Foo() {return 3;},它的两个子类B和C Override了这个Foo()函数。那么在静态编译的时候,C++的编译器不能将Foo()这个函数作inlining优化,因为它不知道在运行的时候到底是A,还是B或是C的Foo()被调用。而Java的虚拟机在运行时就会知道这些信息。如果发现运行的时候是B的Foo()在反复被调用,那么它就大胆的将B的Foo()拿到调用者的程序里面来,这样的inlining处理避免了Function call的开销(仔细说就是No method call;No dynamic dispatch;Possible to constant-fold the value)。对于简单的小函数,调用开销往往比执行还费时间。省略了这些开销性能会成倍的提高。如果这些小函数被上千上万次的调用,那么这样优化下来的效果就非常明显了。这也就是Java在有的时候比C++更快的原因之一。当然,Java做优化实际上相当复杂,因为“大胆”优化有时候也会出现问题。比如将B的Foo()的inlining了,结果突然的蹦出一个对A的Foo()的调用,那程序岂不是要出问题?这个时候呢,Java要退一步,进行反优化(De-optimization),以确保程序的正确。

就这样,Java的虚拟机“骑着毛驴看账本---走着瞧”。一边执行,一边优化,运行不停,优化不止。从外表上看,Java的程序执行会不停的有小的波动。我说的动态优化/反优化就是原因之一(还有很多其他原因)。Java这种特性非常适合长时间运行的服务器端程序,因为这样Hotspot才有足够的机会对程序进行优化。如果程序只是简单的“Hello world”,那Hotspot一点忙帮不上。并且对于“Hello world”这么个简单的程序,一个Java VM也要启动,这就像你点着了一台锅炉,只是想煎一个鸡蛋。好多人觉得Java慢,最初的影像可能就是来源于此。

有人这时候一定会问:“既然这样,那为什么Hotspot不对程序就行全盘优化,那样岂不是更好?”。问题是这样的,优化是有代价的。比如一段程序运行要2毫秒,优化要10毫秒。如果这段程序的执行密度很低,那么Hotspot就会觉得优化不划算而不予优化。你不妨这样想,Hotspot是一个精明的商人,赔本的生意它绝对不会做。

最后再指出一点,那就是Hotspot有客户机和服务器两套(-client, -server),它们有不同的优化方针和策略,具体如何,你可以看看Sun的技术文档。

.NET的JIT

.NET的开发研制是在Java之后,它应该仔细研究过Java的各种有缺点。说实在的,当听说.NET采用的是JIT技术的时候,我有些吃惊。因为这种技术Java早期曾经采用过,后来被Hotspot取代了。不知道为什么微软会捡起来。

点看全图

外链图片需谨慎,可能会被源头改

JIT的工作流程大概是这样的。当它载入一个Type的时候(近似为Java的类),它对这个Type里所有的函数都生成一个Stub。大家可以大概想象为“空壳函数”。当程序执行调用到某一个函数的时候,.NET的虚拟机(CLR, Common Language Runtime)将任务转交给JIT。JIT将这个函数的实体IL代码现场编译成本地机器语言(还要根据.NET的Meta Data),然后将这段程序插入到“空壳函数”中去。从此往后,所有对该函数的调用就是在执行这段机器代码。很简单是吧!至于那些从来没有被调用的函数,CLR也不去理睬它们,任凭那个“空壳函数”挂在那里“随风荡漾”。

就我个人看法,我觉得这种技术比Java的Hotspot要落后,无法达到Java那种“动态优化”的效果。JIT一次编译完成后优化就那样了,不会根据程序运行的特点进行不断的跟踪和调整。

JIT是要花时间的,这会影响程序的性能。并且JIT生成的本地机器代码存储在该程序的内存空间。程序一旦退出,这些代码就消失了。下回你启动运行这个程序,JIT要重复以前的工作。同样的工作反复进行是一种浪费。这些情况微软很清楚。微软的解决办法是"Install-time compilation",或者叫Pre-JIT。在安装.NET Framework的时候,除了将一个个的Assembly拷贝到你的机器上并登记注册外,一个叫做NGen的程序还会将这些Assembly悄悄编译成本地代码,放置在一个秘密的地方(NGen Cache)。这样今后在你的程序调用.NET系统的函数的时候,JIT就不用现场为你编译了,.NET会使用那些事先编译好的本地代码。当然如果你愿意,你可以将你自己开发的应用程序用NGen处理一下,生成Native code。

点看全图

外链图片需谨慎,可能会被源头改

使用NGen有一些注意事项,尤其是.NET 2.0这部分变化比较大,你在使用前自己要好好看看技术文档。

那.NET的程序能跨平台吗?有人一定会问。

答案是可以的。虽然可供跨的平台很有限。比如你可以将你在32位Windows平台上开发的.NET程序直接搬到64位Windows平台上去。你的程序立刻就是64位的了。注意,这一点和其他32位程序不一样,那些程序是在64位平台上的一个微环境里运行(叫做Wow64),它们还是32位程序。而你的.NET程序却是正儿八经的64位程序了。这都是托.NET虚拟机的福。不过你先别高兴太早,要做到这点,你写程序的时候还是有一些制约的。比如你如果在.NET程序里调用了32位的COM程序或是其它32位Native Code,那么你的程序就不可能变成64位的程序了。原因吗,那就要说到64位Windows了,不过那就又是一个大话题了,这里且按下不表。

================================================================================

好了,看到这里,大家可能对Java和.NET的程序运行有了一个了解了。大家是不是觉得微软的.NET有些“面”啊?

我自己认为Technology approach是一个问题,具体的Implementation又是一个问题。评判Java和.NET那个设计的更好不是件简单的事情。就现在的情况来看,这两位各有千秋。比如说Java的Object serialization/de-serialization就做的很出色,.NET built-in的object还不错,但一旦serialization/de-serialization User-defined的Object,问题马上就来了。数据量一大,程序Painful slow。为什么这样我不知道,也是只是.NET 2.0 Beta版本的一个Bug(希望是这样)。而另一方面呢,.NET的Generics非常的先进,是彻头彻尾的Generics,彻底消除了Boxing/Un-boxing和Down-Casting,这方面的性能迅猛提高。而Java的Generics不过是一个“障眼法”,和.NET根本没法比。

Competition makes everybody better。有竞争才会有发展,这是一个最浅显的道理。希望Java和.NET这对冤家对头能相互砥砺,不断的提高,呈现给我们更好的技术。那才是我们广大程序员的福祉!

[MOVE]Long life programming!!![/MOVE]

点看全图

外链图片需谨慎,可能会被源头改

元宝推荐:ArKrXe,Highway, 通宝推:老醋花生,

本帖一共被 1 帖 引用 (帖内工具实现)
全看分页树展 · 主题


有趣有益,互惠互利;开阔视野,博采众长。
虚拟的网络,真实的人。天南地北客,相逢皆朋友

Copyright © cchere 西西河