为什么我支持托管运行时(虚拟机)

  最近博客园上在炒关于C#性能的问题,其实应该说是.NET性能的问题,其中某位仁兄提出,他希望C#能够直接编译为原生代码,而不是在CLR这样一个托管运行时上执行,因为虚拟机啊,JIT什么的性能差。后来发到TL上以后,也有朋友认为,“基于虚拟机的语言都是大公司为了利益在推动,说白了就是政治”,因此“对C#提高性能的建议感到可笑,因为它本来就不是用来开发高性能程序的”,再有,“C、C++已经明确不和这些后进争所谓的‘容易开发’的头衔”,那么其他语言为什么要和C++它们比较性能呢?我是托管运行时,或者虚拟机的忠实拥护者,这里谈一下我在这方面的看法。

  我并不反对编译为原生代码的语言,尤其是C语言,它的意义在于提供了一种对硬件完全控制的手段,对硬件提供了一种最直接的抽象,几乎可以映射到最终流程控制方式,因此无可替代。C++作为C语言的超集,提供了更丰富的抽象能力(如面向对象和模版化),只是语言本身过于复杂,超过了以我的智商可以承受的范围,因此我学了几次都没怎么学会,现在更是忘得差不多了。不过我认为,越来越多的语言会构建在托管平台上,而不是直接编译成原生代码。因为一个统一的托管运行时会带来很多好处。

  首先,统一运行时提供了跨平台的能力,Java便是一典型。.NET上有mono,使用也很广泛,也有不少Unity3D,Gnome DO等成功案例。Novell,包括其他一些公司也在销售基于mono的商业产品(如MonoTouch及Infragistics的ASP.NET Controls组件),我本身也在两年多前就在生产环境上使用了mono,您现在看到的这个博客也是基于Ubuntu Server、mono 2.6 、Apache以及微软开源ASP.NET MVC 2构建的。虽说从某些层面(如API兼容性)上说,mono的跨平台性远不如Java平台,但它也是一个比较成熟的执行环境,并具备相当程度的跨平台能力——尤其是在mono上开发,MS .NET上运行的时候(虽然我不建议这么做)。如今支持mono的产品、类库数不胜数,我时常调侃道,“如果您的产品不支持mono,还真不好意思和人打招呼”。只可惜,不少人都用一些“不是亲娘生的”类似的调调来否定mono,在我看来没有经过调查研究的看法只能属于“臆断”,而且更是一种FUD了。

  即便退一步来说,我们不“跨操作系统”吧。有人说,.NET就支持Windows么,何必搞个虚拟机,还JIT那么麻烦。但事实上,“跨平台”并非指的是简单的“跨操作系统”,而是“跨执行环境”,如Silverlight。事实上,跨计算机体系结构本身也是种跨平台(当然,操作系统其实已经进行了一定的统一抽象了)。因此,虚拟机的目的,是为上层执行体抽象出了统一的运行环境——这其实还是跨平台,这平台不仅仅是指操作系统,整体运行环境之间的差异也是运行时所“抽象”的一部分。比如在并发环境中,不同CPU架构的流水线上的乱序方式不一样,同样的代码执行的效果就可能不同。最典型的例子,便是JVM之前的内存一致性模型控制的比较宽松,导致经典的double check模式在某些CPU上是会失败的。现在Java标准也变得严格了,和.NET CLR一样避免了Store Reordering。这意味着在某些CPU上,会在特定的地方加上Memory Barrier保证执行效果的一致性。在我看来这是更好的可移植性。C++或是C语言等实现“可移植性”的方式,往往是通过为不同环境提供不同的编译器,生成不同的结果,而且会使用“宏”等方式,在代码里写出有平台针对性的代码。

  有了统一的运行时,也可以让多语言互操作更为容易,如果没有JVM或CLR,就很难像现在这么轻松地在Scala/Java/Jython/JRuby,或是C#/F#/IronPython/IronRuby,甚至是未来的语言之间进行直接地互操作,更难做到“无缝集成”了。在合适的场景下选用合适的语言,是提高生产力的重要手段。如果没有JVM平台,就很难使用Scala来代替Java这样的劣质语言,而现在Scala便能够保证充分利用Java平台上类库等积淀。

  有了“多语言”,那么便会引出虚拟机的另一个作用:让语言实现者和虚拟机实现者的工作可以分离开来,各自优化。虚拟机的实现者可以尽力优化虚拟机的各种表现,为虚拟机加入各种优化措施,而无需让虚拟机的上层语言分别调整。例如JVM性能提高之后,ScalaJava、Jython、JRuby等语言的性能都可以提高,否则各种语言分别优化的代价太高了(当然某些情况还是需要特殊对待的)。要说这方面的实例,在.NET平台上有个开源的组件DLR(动态语言运行时),是在CLR上提供编写动态语言的统一辅助类库。以前它有个缺点,便是启动速度比较慢,因为动态代码的JIT耗时较长,而动态语言的很多场景是“即用即抛”的。后来,DLR增加了一个优化策略,便是一开始直接对抽象语法树直接解释执行,而执行多次之后才使用后台线程将代码JIT成原生代码。有了如此改进,基于DLR的动态语言,如IronRuby和IronPython的启动速度都提高了。

  虚拟机/运行时本身可以做的优化也很多,如果真觉得性能不够,那么完全可以在运行前在本地编译成native code,这和直接从源代码变成native code从结果上看没什么区别。但是现实是,很少有人去这么做,因为这么做往往只是节省了JIT的开销,对性能提高效果不大。在不同环境中,此外还有各种优化,比如使用解释执行,而不是JIT以次节省内存消耗,或是在运行时回收JIT的代码(印象中在.NET Compact Framework里有这样的策略,求详情),或是在运行时根据代码逻辑进行二次编译。下面会谈一个例子。另一种典型的优化,一直在研究却还没有真正实现的,虚拟机便是“自动并行”。关于这点,Anders在上次的演讲中多次提到过,要实现这点还需要有各种支持,如声明式编程,提供“无副作用”标记,甚至在语言级别的支持等等。

  之前那位朋友提到,C/C++已经明确不与后进语言比生产力了,后进语言也没办法和它们比较性能。对这个观点我持保留意见,因为基于虚拟机的做法,其优化空间还有很多,理论上也可以做到更为彻底,在许多情况下性能完全有超越静态编译语言的可能。这是许多人的看法,而事实也是如此,在某些场景下Java的性能也已经超过了C++。如回到上面的二次编译优化,对于性能优化也大有好处。举一个简单的例子,面向对象语言会出现很多虚方法调用(尤其是在符合一些关于设计的最佳实践时,如“基于抽象编程”),调用需方法需要查方法表找方法入口,最普通的做法就是必须每次根据对象的实际类型查找方法表,找到地址,然后调用。伪代码如下:

根据实际类型找到函数入口
调用

NET技术为什么我支持托管运行时(虚拟机),转载需保留来源!

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。