西西河

主题:【原创】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 帖 引用 (帖内工具实现)
家园 很精彩的对比!
家园 以后大家还要多切磋才好啊!
家园 花一吨。资源需求方面的要求怎样?
家园 都不是“省油灯”!

可能现在资源太便宜了吧,人们对Resource似乎变得Careless。网上关于Java和.NET的报道对这一方面都不怎么重视。

Java和.NET都有Garbage collector。虽然算法不太一样(非常有讲究,是影响程序性能的一个关键),但任务是相同的。在资源感到有压力的时候,开始清理Heap。那什么时候“资源感到有压力”呢?这个就跟具体的机器配置有关了。清理Heap,回收内存,重新安排Ojbect layout要消耗CPU时间的。所以如果可能,Java和.NET的Garbage collector尽量不出动。这给资源消耗的评测带来了一些困难。

我自己的感觉是它们俩是一个量级的。

家园 切磋切磋

其实小弟对技术性的对比,有点不以为然。

小弟的开发哲学,是不贪心,够用就行。以目前的硬件来看,愚以为不会有太多的应用程序非得用某种平台(.Net/Java)开发不可,不必太过斤斤计较某些部位的速度。而且,某部分的速度优势,又很容易被差劲的设计,二流的程序员抵消。

不过,这种对比,对搞技术的人来讲,还是很过瘾的。

家园 你的话没错。人的素质是非常关键的,即使使用了Java或是

.NET,素质不高的程序员一样能写出来buggy的程序,一样可能有memory leak,虽然有Garbage collector替你负责管理内存。

我之所以关注着两种技术,一是因为工作中可能会遇到,更主要的是我认为这两个阵营中都有牛掰巨擎,IT泰斗。看这些Great mind在尽力厮杀实在是过瘾,欲罢也难啊!

家园 献花。深入浅出,版主出手不凡!

对串行化和范型的看法和我完全一致。

家园 送花。感觉你偏爱java。一个Garbage collector问题。

在其他论坛上看到C++老手们大贬Garbage collector,死也不愿意转到java或.net上来。

我只觉得有了垃圾收集器,编程变得简单了,何乐不为呢。

想听听你的看法。

家园 Garbage collector是Java,.NET的核心部分之一。

其任务很明确:将程序员从繁琐枯燥的Memory Management任务中解放出来,让他们把精力集中到要完成的任务本身上去。

Java和.NET的研制人员都考察过现有的C++的问题。其中资源管理不当是C++程序Bug的一个最主要来源。并且在很多时候这些Bug非常难发现和修改,比如memory leak,有时候要运行很长时间才有可能重现问题。That's really hard to track and debug。

狂扁Garbage collector的C++程序员们都认为自己是C++ guru,那些错误他们根本不会犯。事实上是不是这样我就不知道了。就我个人经历而言,真正的C++大牛都是非常Open minded的。Java以及.NET的设计者都是C++/Unmanage Code出身的,他们之所以创建Java和.NET是因为他们感受到了C++的问题,想提供一个更安全,工作效率更高的环境给广大的程序员。事实上,Java, C#都可以看作是C++派生出来到语言。认为Java和C#是一种倒退是可笑的。

Java以及.NET并不适合所有的任务,C++也是。比如开发Web程序,C++就不合适。而写Device Driver,Java以及.NET就不能胜任。

Garbage collector帮我们解决了很多问题。但并不是程序员从此就不需要考虑资源管理问题了。事实上,有些问题还是很tricky的。比如你是用C#写程序,是不是要给你的Class提供Finalize() Method就是一个难度很大的问题,它极大的影响GC的工作。但我估计很少有人考虑过这个问题。

Garbage collector的内存管理效率比C/C++低吗?这个问题也很难回答。在memory allocate一端,GC非常快,比C/C++还快;在确认un-reachable object, reclaim memory以及Re-arrange heap上GC要花一些时间,对程序性能有影响。所以你的程序最终表现如何,要看你的程序特性了,比如GC是不是要频繁出动。GC在时间上的不确定性对不少应用造成了一些困难,尤其是Java。但对于绝大多数应用而言,我不认为那是一个问题。

微软现在已经将重心转移到了.NET上,虽然Visual C++一直是微软的看家工具,这很说明一个问题,不是吗?

家园 求教一下

为什么“是不是要给你的Class提供Finalize() Method就是一个难度很大的问题”

小弟一般的习惯是假如有一些Class Level的Object,就会在Finalize() Method清除。

要是老轧懒得打字,指点一些文章,书籍也行。

家园 把这个问题搞清楚了,.NET的GC你就门清了。

具体原因吗,请看

1)外链出处

2)外链出处

家园 先谢再看!
家园 受教,花一吨
家园 我来举个实例,支持垃圾收集器

某项目中,我同事的任务是在Unix下用ANSI C做开发,需要用到malloc()和free()做内存的动态分配和释放。编码完成之后,他在某处碰到coredump问题,整整花了三天时间,没有找出原因,整个人状态极差,萎靡不振。我发觉了,预感这样下去这家伙会辞职。于是,主动要求帮他查错,花了两三个小时之后,我终于定位到问题所在:不该用free()的地方错调了。改了那个错,他的程序一泻千里,顺利通过。可是,不出我所料,他提出辞职,原因是:不能做好该类开发工作。

作为一个首次用C语言在Unix下开发的程序员,他的表现其实已经很令我满意,至今我都认为他是个优秀的程序员。可是,因为这该死的free,他大为自责,最终还是辞职了。

所以,无论编程人员水平如何,简化编程都很有意义。

诋毁Garbage collector的人,只是为了保持自己的优越感……

全看树展主题 · 分页 下页


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

Copyright © cchere 西西河