這是摘自Unity官方文檔有關(guān)優(yōu)化的部分纺蛆,原文鏈接:https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity.html
總共分為如下系列:
- 采樣分析
- 內(nèi)存部分
- 協(xié)程
- Asset審查
- 理解托管堆 【推薦閱讀】
5.1 上篇:原理负懦,臨時分配內(nèi)存,集合和數(shù)組
5.2 下篇:閉包,裝箱,數(shù)組 - 字符串和文本
- 資源目錄
- 通用的優(yōu)化方案
- 一些特殊的優(yōu)化方案
采樣分析
考慮到優(yōu)化性能的時候,首先必須記住所有的優(yōu)化是從采樣分析開始。對應(yīng)用進(jìn)行采樣發(fā)現(xiàn)問題所在是第一步芍殖,從而對采樣的結(jié)果針對項目的代碼和資源進(jìn)行分析。
工具:
- iOS:Instrument和XCode Frame Debugger
- Android:Snapdragon Profiler
- Intel CPU/GPU的平臺:VTune和Intel GPA
- PS4:Razor套裝
- Xbox:Pix工具
這些工具的使用條件是IL2CPP生成的項目谴蔑,因為原生C++代碼能夠提供更加清晰的調(diào)用關(guān)系和方法調(diào)用的時間消耗豌骏,在Mono平臺下做不到這些。
啟動日志的分析
當(dāng)觀察啟動時的跟蹤日志树碱,需要特別關(guān)注兩個關(guān)鍵方法肯适,這兩個方法反映了項目中的配置、資源和代碼如何影響啟動時間成榜。
游戲啟動設(shè)置不同的平臺上略有差異框舔,在大部分平臺上用戶可以看到一個靜態(tài)的啟動界面。
上圖是來自于Instrument工具截取在iOS設(shè)備上運行實例工程的跟蹤日志。在startUnity方法中刘绣,注意UnityInitApplicationGraphics
和UnityLoadApplication
方法樱溉。
UnityInitApplicationGraphics
這個方法會執(zhí)行一系列的內(nèi)部操作,設(shè)置圖形顯示設(shè)備和初始化Unity的內(nèi)部系統(tǒng)纬凤。除此之外福贞,它還會初始化資源系統(tǒng),導(dǎo)入資源系統(tǒng)包含的所有文件的索引停士。
每個“Resources”目錄中包含的資源文件都屬于資源系統(tǒng)的數(shù)據(jù)挖帘。
【Resources”目錄指的是“Assets”目錄下的“Resources”以及這些“Resources”目錄下的所有子文件夾×导迹】
所以拇舀,如果Resources目錄下的文件非常多,就會造成加載的時間比較長蜻底。
UnityLoadApplication
包含加載和初始化項目中的第一個場景骄崩。這個步驟包括反序列化和實例化場景中所有的數(shù)據(jù),例如編譯Shader薄辅,上傳紋理和實例化GameObject要拂。除此之外,第一個場景中的所有MonoBehaviour對象中的Awake方法都在這個方法內(nèi)執(zhí)行站楚。
這些處理流程意味著脱惰,如果你的Awake回調(diào)方法中有耗時較長的代碼,就會嚴(yán)重拖慢項目的啟動速度源请。要不你就刪除這段代碼枪芒,要不你就把這段代碼放到別的生命周期中執(zhí)行。
運行日志的分析
在啟動部分完成之后谁尸,接著就是PlayerLoop
方法了。這是Unity的主循環(huán)纽甘,每幀都會執(zhí)行一次良蛮。
上圖中的截圖來自于Unity5.4的某個示例工程,展示了PlayerLoop中值得關(guān)注的幾個方法悍赢。注意PlayerLoop中的方法在不同版本的Unity中可能不同决瞳。
PlayerRender
運行Unity的渲染系統(tǒng),包括剔除不需要顯示的對象左权,計算動態(tài)合批皮胡,給GPU提交繪制指令。圖片特效或者基于渲染的腳本中的回調(diào)(如OnWillRenderObject
)都會在這個函數(shù)中執(zhí)行赏迟。一般情形下屡贺,當(dāng)項目是可以UI交互的時候,這個方法會是消耗CPU資源的頭號顧客。
BaseBehaviourManager
調(diào)用三種模板化的CommonUpdate方法甩栈。這些方法調(diào)用當(dāng)前場景中活躍的GameObject上 MonoBehaviour中特定的回調(diào)方法泻仙。
- CommonUpdate<UpdateManager> 調(diào)用Update回調(diào)
- CommonUpdate<LateUpdateManager> 調(diào)用LateUpdate回調(diào)。
- CommonUpdate<FixedUpdateManager> 調(diào)用FixedUpdate回調(diào)量没。
通常來講玉转,BaseBehaviourManager::CommonUpdate<UpdateManager>
是最值得關(guān)注的方法,因為它是Unity工程中大部分代碼的入口方法殴蹄。
其他值得關(guān)注的代碼:
UI::CanvasManager
如果工程用到了Unity UI究抓,這個方法會調(diào)用幾個不同的回調(diào)函數(shù)。包括Unity UI的批處理過程和布局更新袭灯;這兩個操作會導(dǎo)致CanvasManager出現(xiàn)在剖析日志列表中刺下。
DelayedCallManager::Update
運行協(xié)程。更多的細(xì)節(jié)可以參見系列文章中“協(xié)程”這一章妓蛮。
PhysicsManager::FixedUpdate
運行PhysX物理系統(tǒng)怠李,這個函數(shù)主要運行PhysX的內(nèi)部代碼,受到當(dāng)前場景中物理組件個數(shù)的影響蛤克,如Rigidbody和Collider組件捺癞。還有,基于物理的回調(diào)也會出現(xiàn)在這個函數(shù)构挤,如OnTriggerStay和OnCollisionStay方法髓介。
如果工程中用到2D物理,在Physics2DManager::FixedUpdate方法下面會出現(xiàn)類似的方法筋现。
腳本方法的分析
當(dāng)IL2CPP轉(zhuǎn)換后的代碼被調(diào)用的時候唐础,可以找到ScriptingInvocation
對象。這是Unity的原生代碼過渡到腳本運行環(huán)境下矾飞,執(zhí)行腳本代碼的關(guān)鍵部分一膨。【從技術(shù)細(xì)節(jié)上來講洒沦,當(dāng)使用IL2CPP豹绪,C#的代碼也會被轉(zhuǎn)化成原生代碼。然而編譯后的代碼通過IL2CPP運行框架執(zhí)行方法的過程申眼,和手寫的C++代碼執(zhí)行過程不同】
上面的截圖來自Unity5.4上的一個示例工程瞒津。RuntimeInvoker_Void
行下面包含的所有函數(shù)都是交叉編譯后的C#代碼,這些方法每幀執(zhí)行一次括尸。
被轉(zhuǎn)化后的方法命名規(guī)則是巷蚪,每個方法是“類名_原始方法名”。在這些跟蹤日志里面濒翻,有EventSystem.Update
屁柏,PlayerShooting.Update
和一些其他的Update方法啦膜。這些是大多數(shù)MonoBehaviour類中的Update回調(diào)方法。
展開這些方法前联,很容易發(fā)現(xiàn)到底是哪些方法消耗了大部分的CPU時功戚,也包括其他的腳本中的方法,如Unity的API和C# 庫中的代碼似嗤。
上面的跟蹤日志展示了StandaloneInputModule.Process
需要給整個UI系統(tǒng)投射射線啸臀,檢測什么觸碰事件正在懸停或者激活某個UI元素烁落。最大的開銷在于遍歷所有的UI元素和檢測鼠標(biāo)的位置是否在這些UI元素的邊界范圍之內(nèi)乘粒。
資源加載
資源加載也會出現(xiàn)在CPU的日志中。資源加載的主要方法是SerializedFile::ReadObject
伤塌。這個方法通過執(zhí)行Transfer方法把文件的二進(jìn)制數(shù)據(jù)流和Unity的序列化系統(tǒng)連接起來灯萍。Transfer方法可以在所有的資源類型中找到,如紋理每聪,MonoBehaviour和粒子系統(tǒng)旦棉。
上面的截圖展示了場景正在加載的日志。這個過程需要Unity去讀取數(shù)據(jù)和反序列化場景中所有的資源药薯,通過調(diào)用SerializedFile::ReadObject方法下的不同資源類型的Transfer方法绑洛。
通常來講,如果在運行時間存在性能問題童本,而且SerializedFile::ReadObject
方法占據(jù)了很大的性能開銷的話真屯,很有可能就是資源加載導(dǎo)致的幀率問題。注意穷娱,在大多數(shù)情況下绑蔫,如果是 SceneManager,Resources和AssetBundle API這些方法發(fā)起同步資源加載的話泵额,SerializedFile::ReadObject方法只能在主線程中看到配深。
這種性能問題可以通過常用的方法解決:使用異步加載,將ReadObject方法交給worker線程去做嫁盲,或者提前加載比較大的資源凉馆。
注意,Transfer調(diào)用在克隆Object的時候也可能會出現(xiàn)亡资,由CloneObject方法調(diào)用。如果CloneObject方法里面調(diào)用了Transfer方法向叉,那么表明資源不是從存儲器中加載锥腻,而是將老Object的數(shù)據(jù)傳遞給了新Object。Unity序列化老的Object并且根據(jù)這些數(shù)據(jù)反序列化出了新的Object母谎。
下篇:內(nèi)存分析
如果覺得文章對您有用瘦黑,請點個贊唄!???