|
最近博客園上在炒關于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性能提高之后,Scala、Java、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技術:為什么我支持托管運行時(虛擬機),轉載需保留來源!
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。