官术网_书友最值得收藏!

第2章 C#技術要點

2.1 Unity3D中C#的底層原理

Unity底層在運行C#程序時有兩種機制,一種是Mono,另一種是IL2CPP。這兩種機制到底有什么區別?又是如何運作的呢?下面來簡單介紹一下。

我們知道,在Unity中使用C#語言的一個重要好處就是編譯快且開發效率高。但是.NET雖好卻只能運行在Windows上(不過現在NetCore可以實現多平臺了,只是還不夠完善),主要是因為微軟沒有考慮跨平臺而且沒有將其開源。后來微軟公司向ECMA申請將C#作為一種標準。C#在2003年成為一個ISO標準(ISO/IEC 23270)。這意味著只要是遵守CLI(Common Language Infrastructure)的第三方就可以將任何一種語言實現到.NET平臺上。Mono就是在這種環境下誕生的,Mono的目標就是達成跨平臺.NET 4.0的完整功能支持。

與微軟的.NET Framework(共通語言運行平臺)不同,Mono項目不僅可以運行于Windows系統上,還可以運行于Linux、FreeBSD、Unix、OS X和Solaris上,甚至是一些游戲平臺上,例如:Playstation 3、Wii或XBox 360。Mono使得C#這門語言有了很好的跨平臺能力。

這里要引出一個很重要的概念“IL”。IL的全稱是Intermediate Language,但很多時候我們看到的是CIL(Common Intermediate Language,特指在.NET平臺下的IL標準),其實大部分文章中提到的IL和CIL表示的是同一個東西,即中間語言。

IL是一種低階(lowest-level)的人類可讀的編程語言。我們可以將通用語言翻譯成IL,然后匯編成字節碼,最后運行在虛擬機上;也可以把IL看作一個面向對象的匯編語言,只是它必須運行在虛擬機上,而且是完全基于堆棧的語言。也就是說,C#、VB、J#這種遵循CLI規范的高級語言,會被各自的編譯器編譯成中間語言IL(CIL),當需要運行它們時就會被實時地加載到運行時庫中,由虛擬機動態地編譯成匯編代碼(JIT)并執行。

值得注意的是,在Unity中,其他兩門腳本語言Boo和Unity Script也同樣是被各自的編譯器編譯成遵循CLI規范的IL后,再由Mono虛擬機解釋并執行。

其實IL有三種轉譯模式。

·Just-in-time(JIT)編譯:在程序運行過程中將CIL轉譯為機器碼。

·Ahead-of-Time(AOT)編譯:將IL轉譯成機器碼并存儲在文件中,此文件并不能完全獨立運行。通常此種模式可產生出絕大部分JIT模式所產生的機器碼,只是有部分例外,例如trampolines或是控管監督相關的代碼仍舊需要JIT來運行。

·完全靜態編譯:這個模式只支持少數平臺,它基于AOT編譯模式更進一步產生所有的機器碼。完全靜態編譯模式可以讓程序在運行期完全不需要用到JIT,這個做法適用于iOS操作系統、PlayStation 3以及XBox 360等不允許使用JIT的操作系統。

Unity在打包iOS操作系統的時候就使用了第三種方式,而在Android和Windows上則使用JIT實時編譯來運行代碼。

Mono內包含三類組件,分別是核心組件、Mono/Linux/GNOME開發堆棧、微軟兼容堆棧。下面簡單介紹一下它們:

·核心組件包含C#編譯器、Common Language Infrastructure虛擬機,以及核心類別程序庫。

·Mono/Linux/GNOME開發堆棧提供了工具用于開發應用軟件。這些工具使用了既有的GNOME以及自由且開放源代碼的程序庫,它們包含了針對圖形用戶界面開發的Gtk#、可套用Gecko rendering engine的Mozilla程序庫、Unix集成程序庫(Mono.Posix)、安全堆棧,以及XML schema語言RelaxNG。

·微軟兼容堆棧提供了一種方式以使Windows .NET應用程序可以被移植到GNU/Linux上,堆棧包含了ADO.NET、ASP.NET以及Windows Forms等。

Mono使用垃圾回收機制來管理內存,應用程序向垃圾回收器申請內存,最終由垃圾回收器決定是否回收。當我們向垃圾回收器申請內存時,如果發現內存不足,就會自動觸發垃圾回收,或者也可以主動觸發垃圾回收,垃圾回收器此時會遍歷內存中所有對象的引用關系,如果沒有被任務對象引用則會釋放內存。

在3.1.1版之后Mono正式將Simple Generational GC(SGen-GC)設置為默認的垃圾回收器,它比前面幾代的垃圾回收器要好用得多。SGen-GC的主要思想是將對象分為兩個內存池,一個較新,一個較老,那些存活時間長的對象都會被轉移到較老的內存池中去。這種設計是基于這樣的一個事實:程序經常會申請一些小的臨時對象,用完了馬上就釋放。而如果某個對象一段時間沒被釋放,往往很長時間都不會釋放。

前面說到IL編碼,其實C#代碼生成的IL編碼我們稱為托管代碼,由虛擬機的JIT編譯執行,其中的對象無須手動釋放,它們由GC管理。C/C++或C#中以不安全類型寫的代碼我們稱為非托管代碼,虛擬機無法跟蹤到這類代碼對象,因此在Unity中有托管代碼和非托管代碼之分。

一般情況下,我們使用托管代碼來編寫游戲邏輯,非托管代碼通常用于更底層的架構、第三方庫或者操作系統相關接口,非托管代碼使用這部分的內存必須由程序員自己來管理,否則會造成運行時錯誤或者內存泄漏。這就像Android中的Java和Native code的區別一樣,Java的bytecode是跑在虛擬機上的,而Native code則直接跑在bare metal上。為了訪問底層資源,Java中的部分接口最終還是要通過JNI調到Native code中來。Mono的框架其實也是類似的,IL代碼要實現與平臺相關的調用或是調用已有的Native library,最終還是要通過一套類似于JNI的接口實現,同時Mono自己也有一套可以使用非托管代碼的方法。

Unity的Mono用得好好的,為什么要加入IL2CPP機制呢?Unity官方解釋的原因有以下幾個:

1)維護成本過大。Unity的Mono虛擬機有自己的修改方案,需要自己維護獨有的虛擬機程序。這導致Unity在各個平臺完成移植工作時,工作量巨大,有時甚至不可能完成。在這種情況下,每新增一個平臺,Unity的項目組就要把虛擬機移植一遍,同時要解決不同平臺虛擬機里的問題。而像WebGL這樣基于瀏覽器的平臺的移植工作甚至不太可能完成。

2)Mono版本授權受限。Mono版本無法升級,這也是Unity社區開發者抱怨最多的一條,很多C#的新特性無法使用。如果換成IL2CPP,則可以通過IL2CPP自己開發一套組件來解決這個問題。

3)提高運行效率。根據官方的實驗數據,換成IL2CPP以后,程序的運行效率有了1.5~2.0倍的提升。

那么IL2CPP的編譯和運行過程是怎么樣的呢?

首先還是由Mono將C#語言翻譯成IL,IL2CPP在得到中間語言IL后,將它們重新變回C++代碼,再由各個平臺的C++編譯器直接編譯成能執行的機器碼。

這里要注意的是,雖然C#代碼被翻譯成了C++代碼,但IL2CPP也有自己的虛擬機,IL2CPP的虛擬機并不執行JIT或者翻譯任何代碼,它主要是用于內存管理,其內存管理仍然采用類似Mono的方式,因此程序員在使用IL2CPP時無須關心Mono與IL2CPP之間的內存差異。

前面已提到,Unity在iOS平臺中使用基于AOT的完全靜態編譯繞過了JIT,使得Mono能在這些不支持JIT的操作系統中使用。對于IL2CPP來說,其實就相當于靜態編譯了C#代碼,只是這次編譯成了C++代碼,最后翻譯成二進制機器碼繞過了JIT,所以也可以說IL2CPP實現了另一種AOT完全靜態編譯。

主站蜘蛛池模板: 自贡市| 兴仁县| 长葛市| 三明市| 尼勒克县| 修文县| 奉新县| 荥阳市| 巴塘县| 巴楚县| 湘西| 怀化市| 昂仁县| 出国| 山阳县| 甘泉县| 普格县| 揭阳市| 礼泉县| 临澧县| 濮阳市| 铁力市| 遵化市| 井陉县| 银川市| 伊金霍洛旗| 隆回县| 镇赉县| 灵山县| 东山县| 舟曲县| 桑日县| 宁国市| 万荣县| 小金县| 漠河县| 宜章县| 朝阳县| 南充市| 满洲里市| 大田县|