天天看點

【轉】【UNITY3D 遊戲開發之十】關于IL2CPP(支援IOS-64BIT)的深入講解以及UNITY優化方面的幾篇文章

我們沒有停止改進il2cpp的打算,但是在目前這個時間點上,我們覺得可以回過頭來抽出點時間告訴大家一些il2cpp的内部工作機制。在接下來的幾個月的時間裡,我們打算對以下話題(或者還有其他未列出的話題)進行讨論,來做一個il2cpp深入講解系列。目前準備讨論的話題有:

1.基礎 – 工具鍊和指令行參數(本篇博文)

2.il2cpp生成代碼介紹

3.il2cpp生成代碼調試小竅門

4.方法調用介紹(一般方法調用和虛方法調用等)

5. 通用代碼共享的實作

6.p/invoke(platform invocation service)對于類型(types)和方法(methods)的封裝

7.垃圾回收器的內建

8.測試架構(testing frameworks)及其使用

為了能讓這個系列的讨論成為可能,我們會涉及到一些将來肯定會進行改動的il2cpp的實作細節。但這也沒有關系,通過這些讨論,我們希望能給大家提供一些有用和有趣的資訊。

什麼是il2cpp?

從技術層面上來說,我們說的il2cpp包含了兩部分:

        一個進行 預先編譯(譯注:ahead-of-time,又叫aot,以下一律使用aot縮寫)的編譯器

        一個支援虛拟機的運作時庫

aot編譯器将由.net 輸出的中間語言(il)代碼生成為c++代碼。運作時庫則提供諸如垃圾回收,與平台無關的線程,io以及内部調用(c++原生代碼直接通路托管代碼結構)這樣的服務和抽象層。

aot編譯器

il2cpp aot編譯器實際的執行檔案是il2cpp.exe。在windows平台你可以在unity安裝路徑的editor\data\il2cpp目錄下找到。對于osx平台,它位于unity安裝路徑的contents/frameworks/il2cpp/build目錄内。 il2cpp.exe這個工具是一個托管代碼可執行檔案,其完全由c#寫成。在開發il2cpp的過程中,我們同時使用.net和mono編譯器對其進行編譯。

il2cpp 接受來自unity自帶的或者由mono編譯器産生的托管程式集,将這些程式集轉換成c++代碼。這些轉換出的c++代碼最終由部署目标平台上的c++編譯器進行編譯。

你可以參照下圖了解il2cpp工具鍊的作用:

【轉】【UNITY3D 遊戲開發之十】關于IL2CPP(支援IOS-64BIT)的深入講解以及UNITY優化方面的幾篇文章

運作時庫

il2cpp的另外一個部分就是對虛拟機提供支援的運作時庫。我們基本上是用c++代碼來實作整個運作時庫的(好吧,其實裡面還是有一些和平台相關的代碼使用了程式集,這個隻要你知我知便好,不要告訴别人 )。我們把運作時庫稱之為libli2cpp,它是作為一個靜态庫被連接配接到最終的遊戲可執行檔案中。這麼做的一個主要的好處是可以使得整個il2cpp技術是簡單并且是可移植的。

你能通過檢視随unity一起釋出的libil2cpp頭檔案來窺探其代碼組織方式(windows平台,頭檔案在editor\data\playbackengines\webglsupport\buildtools\libraries\libil2cpp\include目錄中。osx平台,頭檔案在contents/frameworks/il2cpp/libil2cpp目錄中)。舉個例子,由il2cpp産生的c++代碼和libil2cpp之間的接口api,存在于codegen/il2cpp-codegen.h這個檔案中。

il2cpp是如何執行的?

讓我們從一個簡單的例子入手。這裡使用unity的版本是5.0.1,在windows環境并且建立一個全新的空項目。然後建立一個帶monobehaviour的腳本檔案,将其作為元件加入到main camera上。代碼也是非常的簡單,輸出hello world:

1

2

3

4

5

6

7

using unityengine;

public class helloworld : monobehaviour {

  void start () {

    debug.log(“hello, il2cpp!”);

  }

}

“c:\program files\unity\editor\data\monobleedingedge\bin\mono.exe” “c:\program files\unity\editor\data\il2cpp/il2cpp.exe” –copy-level=none –enable-generic-sharing –enable-unity-event-support –output-format=compact –extra-types.file=”c:\program files\unity\editor\data\il2cpp\il2cpp_default_extra_types.txt” “c:\users\josh peterson\documents\il2cpp blog example\temp\stagingarea\data\managed\assembly-csharp.dll” “c:\users\josh peterson\documents\il2cpp blog example\temp\stagingarea\data\managed\unityengine.ui.dll” “c:\users\josh peterson\documents\il2cpp blog example\temp\stagingarea\data\il2cppoutput”

嗯,這個真是老太太的裹腳布 – 又臭又長……,是以讓我們把指令分拆一下,unity運作的是這個可執行檔案:

“c:\program files\unity\editor\data\monobleedingedge\bin\mono.exe”

下一個參數是il2cpp.exe工具本身:

“c:\program files\unity\editor\data\il2cpp/il2cpp.exe”

請注意剩下的參數其實都是傳遞給il2cpp.exe的而不是mono.exe。上面的例子裡傳遞了5個參數給il2cpp.exe:

–copy-level=none

指明il2cpp.exe不對生成的c++檔案進行copy操作

–enable-generic-sharing

告訴il2cpp如果可以,對通用方法進行共享。這個可以減少代碼并降低最後二進制檔案的尺寸

–enable-unity-event-support

確定和unity events相關的,通過反射機制來運作的代碼,能夠正确生成。

–output-format=compact

在生成c++代碼時為裡面的類型和方法使用更短的名字。這會使得c++代碼難以閱讀,因為原來在il中的名字被更短的取代了。但好處是可以讓c++編譯器運作的更快。

–extra-types.file=”c:\program files\unity\editor\data\il2cpp\il2cpp_default_extra_types.txt”

使用預設的(也是空的)額外類型檔案。il2cpp.exe會将在這個檔案中出現的基本類型或者數組類型看作是在運作時生成的而不是一開始出現在il代碼中來對待。

需要注意的是這些參數可能會在以後的unity版本中有所變化。我們現在還沒有穩定到把il2cpp.exe的指令行參數整理固定下來的階段。

最後,我們有由兩個檔案組成的一個清單和一個目錄在這個長長的指令行中:

“c:\users\josh peterson\documents\il2cpp blog example\temp\stagingarea\data\managed\assembly-csharp.dll”

“c:\users\josh peterson\documents\il2cpp blog example\temp\stagingarea\data\managed\unityengine.ui.dll”

“c:\users\josh peterson\documents\il2cpp blog example\temp\stagingarea\data\il2cppoutput”

il2cpp.exe工具可以接收一個由il程式集組成的清單。在上面這個例子中,程式集包含了項目中的簡單腳本程式集:assembly-csharp.dll,和gui程式集:unityengine.ui.dll。大家可能會注意到這裡面明顯少了什麼:unityengine.dll到哪去了?系統底層的mscorlib.dll也不見了蹤影。實際上,il2cpp.exe會在内部自動引用這些程式集。你當然也可以把這些放入清單中,但他們不是必須的。你隻需要提及那些根程式集(那些沒有被其他任何程式集引用到的程式集),剩下的il2cpp.exe會根據引用關系自動加入。

裹腳布的最後一塊是一個目錄,il2cpp.exe會将最終的c++代碼生成到這裡。如果你還保持着一顆好奇的心,可以看看這個目錄中産生的檔案。這些檔案是我們下一個讨論的主題。在你審視這些代碼前,可以考慮将webgl建構設定中的“development player”選項勾上。這麼做會移除–output-format=compact指令行參數進而讓c++代碼中的類型和方法的名字更加可讀。

嘗試在webgl或者ios建構設定中進行些改變。這樣你會發現傳遞給il2cpp.exe的參數也會相應的發生變化。例如,将“enable exceptions” 設定成“full” 會将–emit-null-checks,–enable-stacktrace,和 –enable-array-bounds-check這三個參數加入il2cpp.exe指令行。

il2cpp沒做的事情

我想指出il2cpp有一向挑戰我們沒有接受,而且我們也高興我們忽略了它。我們沒有嘗試重寫整個c#标準庫。當你使用il2cpp後端建構unity項目的時候,所有在mscorlib.dll,system.dll等中的c#标準庫和原來使用mono編譯時候的一模一樣。

我們可以依賴健壯的且久經考驗的c#标準庫,是以當處理有關il2cpp的bug的時候,我們可以很肯定的說問題出在aot編譯器或者運作時庫這兩個地方而不是在其他地方。

我們如何開發,測試,釋出il2cpp

自從我們在一月份的4.6.1 p5版本中首次引入il2cpp以來,我們已經連續釋出了6個unity版本和7個更新檔(unity版本号跨越4.6和5.0)。在這些釋出中我們修正了超過100個bug。

為了確定持續的改進得以實施,我們内部隻保留一份最新的開發代碼在主幹分之(trunk branch)上,在釋出各個版本之前,我們會将il2cpp的改動挂到一個特定的分之下,然後進行測試,確定所有的bug已經正确的修正了。我們的qa和維護工作組為此付出了驚人的努力才得以保證釋出版本的快速疊代。(譯注:感覺是版本管理的标準的開發流程,另外由文中提到的trunk branch來看,他們貌似還在使用svn)

提供高品質bug的使用者社群被證明是一個無價之寶。我們非常感謝使用者的回報來幫助我們改進il2cpp,并且希望這類回報越多越好。

好戲連台

關于il2cpp我們還有很多可以說的。下一次我們會深入到il2cpp.exe代碼生成的細節中。看看對于c++編譯器來說,由il2cpp.exe生成的代碼會是個什麼樣子。

項目進入了中期之後,就需要對程式在移動裝置上的表現做分析評估和針對性的優化了,首先前期做優化,很多瓶頸沒表現出來,能做的東西不多,而且很多名額會憑預想,如果太後期做優化又會太晚,到時發現一些問題改起來返工量就有太大。前一陣子花了大量時間從 cpu gpu 記憶體 啟動時間 到發熱量對項目做了一翻大規模的體檢和優化,效果還是顯著的,在這裡做個筆記,以後開發項目時可以作為經驗和提前關注

1.項目情況:筆者所在項目是一個非常重度的手遊,甚至開始就是瞄着端遊做的,3d世界,2.5d視角,rpg,即使戰鬥,美術品質要求極高(模型 貼圖精度高 ,超過目前市場同類産品)。對于目前大多數移動裝置來看,挑戰不小,對手機的各種硬體都是挑戰。、

2.目标機型:偏中高端,盡量相容低端,android至少sumsung s3能流暢,ios至少iphone4s流暢。

3.性能名額:記憶體占用250m以下(這樣大量512的機器不會挂掉),初始包100m之内(太多營運不幹,太少實在是裝不下。。)

1.首先是unity在編輯器下的statics視窗:提供了dc和頂點數這兩個重要名額的檢視。缺點在裝置看不到,但是對于dc數和頂點數來說,裝置和編輯器差不多,用它可以大體看出渲染的壓力。

2.unity自帶的profiler:可以連接配接裝置看到裝置上cpu gpu mem的資訊,使用的時候需要勾選development模式。有點是cpu的占用在腳本的層面看的非常仔細,哪個函數占用了太多時間一眼就能看出,基本是分析腳本效率的最佳工具,但是gpu大部分裝置不支援看不到,顯示的mem資訊不太準确,基本上偏離實際占用的記憶體

adb shell dumpsys cpuinfo appname 檢視實時的cpu占用,注意這裡的cpu可能過百,這是因為多核的原因

adb shell dumpsys gpuinfo appname 檢視實時的gpu情況

5.android  的monitor

安裝adt後,在sdk\tools\monitor.bat下面有個monitor,是我認為android看性能最好的工具之一,因為它是圖形化的,而且基本內建了adb的功能,從記憶體到cpu到gpu,還有很有用的網絡流量使用情況,它的cpu占用是c++層面的統計,看不到腳本,這需要突破那個profilor結合。

6.android上的mongkey測試:它可以模拟随機的使用者輸入,用來驗證你的程式的強壯性吧

adb shell monkey -p -v packname 1000

随機模拟1000條使用者事件

7.ios:ios上的工具則顯得更加專業更加統一一些,ios就用xcode自帶的instruments了

看來這麼多工具,其實很多是要配合使用的,做u3d開發,其實不隻是學會u3d的事情,要讓u3d在手機上運作的好,還需要對各個平台有較深的了解。

1.使用assetbundle,實作資源分離和共享,将記憶體控制到200m之内,同時也可以實作資源的線上更新

2.頂點數對渲染無論是cpu還是gpu都是壓力最大的貢獻者,降低頂點數到8萬以下,fps穩定到了30幀左右

3.隻使用一盞動态光,不是用陰影,不使用光照探頭

粒子系統是cpu上的大頭

4.剪裁粒子系統

5.合并同時出現的粒子系統

6.自己實作輕量級的粒子系統

animator也是一個效率奇差的地方

7.把不需要跟骨骼動畫和動作過渡的地方全部使用animation,控制骨骼數量在30根以下

8.animator出視野不更新

9.删除無意義的animator

10.animator的初始化很耗時(粒子上能不能盡量不用animator)

11.除主角外都不要跟骨骼運動apply root motion

12.絕對禁止掉那些不帶剛體帶包圍盒的物體(static collider )運動

nugi的代碼效率很差,基本上runtime的時候對cpu的貢獻和render不相上下

13每幀遞歸的計算finalalpha改為隻有初始化和變動時計算

14去掉法線計算

15不要每幀計算viewsize 和windowsize

16filldrawcall時建構頂點緩存使用array.copy

17.代碼剪裁:使用strip level ,使用.net2.0 subset

18.盡量減少smooth group

19.給美術定一個嚴格的經過科學驗證的美術标準,并在u3d裡面配以相應的檢查工具