Unity 性能優化總結—CPU篇
性能優化是遊戲項目開發過程中一個永恆的話題。玩家的需求和項目的要求永遠在不停增長,同屏人數、屏幕特效和場景複雜度永遠在向著「榨乾」硬體的趨勢逼近。所以,無論硬體設備發展到何種程度,無論研發團隊有多麼豐富的經驗積累,性能優化永遠是一個非常棘手而又無法繞開的問題。
就當前遊戲而言,性能優化主要是圍著CPU、GPU和內存三大方面進行。下面,我們就這三方面來說說當前移動遊戲項目中存在的普遍問題和相應的解決方案。
CPU方面
就目前的Unity移動遊戲而言,CPU方面的性能開銷主要可歸結為兩大類:引擎模塊性能開銷和自身代碼性能開銷。其中,引擎模塊中又可細緻劃分為渲染模塊、動畫模塊、物理模塊、UI模塊、粒子系統、載入模塊和GC調用等等。正因如此,我們在UWA測評報告中,就這些模塊進行詳細的性能分析,以方便大家快速定位項目的性能瓶頸,同時,根據我們的分析和建議對問題進行迅速排查和解決。
通過大量的性能測評數據,我們發現渲染模塊、UI模塊和載入模塊,往往佔據了遊戲CPU性能開銷的Top3。
一、渲染模塊
渲染模塊可以說是任何遊戲中最為消耗CPU性能的引擎模塊,因為幾乎所有的遊戲都離不開場景、物體和特效的渲染。對於渲染模塊的優化,主要從以下兩個方面入手:
(1)降低Draw Call
Draw Call是渲染模塊優化方面的重中之重,一般來說,Draw Call越高,則渲染模塊的CPU開銷越大。究其原因,要從底層Driver和GPU的渲染流程講起,限於篇幅我們不在這裡做過多的介紹。有興趣的朋友可以查看http://stackoverflow.com/questions/4853856/why-are-draw-calls-expensive,或者自行Google相關的技術文獻。
降低Draw Call的方法則主要是減少所渲染物體的材質種類,並通過Draw Call Batching來減少其數量。Unity文檔對於Draw Call Batching的原理和注意事項有非常詳細的講解,感興趣的朋友可以直接查看 Unity官方文檔。
但是,需要注意的是,遊戲性能並非Draw Call越小越好。這是因為,決定渲染模塊性能的除了Draw Call之外,還有用於傳輸渲染數據的匯流排帶寬。當我們使用Draw Call Batching將同種材質的網格模型拼合在一起時,可能會造成同一時間需要傳輸的數據(Texture、VB/IB等)大大增加,以至於造成帶寬「堵塞」,在資源無法及時傳輸過去的情況下,GPU只能等待,從而反倒降低了遊戲的運行幀率。
Draw Call和匯流排帶寬是天平的兩端,我們需要做的是儘可能維持天平的平衡,任何一邊過高或過低,對性能來說都是無益的。
(2)簡化資源
簡化資源是非常行之有效的優化手段。在大量的移動遊戲中,其渲染資源其實是「過量」的,過量的網格資源、不合規的紋理資源等等。所以,我們在UWA測評報告中對資源的使用進行了詳細的展示(每幀渲染的三角形面片數、網格和紋理資源的具體使用情況等),以幫助大家快速查找和完善存在問題的資源。
關於渲染模塊在CPU方面的優化方法還有很多,比如LOD、Occlusion Culling和Culling Distance等等。我們會在後續的渲染模塊技術專題中進行更為詳細的講解,敬請期待。
二、UI模塊
UI模塊同樣也是幾乎所有的遊戲項目中必備的模塊。一個性能優異的UI模塊可以將遊戲的用戶體驗再抬高一個檔次。在目前國內的大量項目中,NGUI作為UI解決方案的佔比仍然非常高。所以,UWA測評報告對NGUI的性能分析進行了極大的支持,我們會根據用戶所使用的UI解決方案(UGUI或NGUI)的不同提供不同的性能分析和優化建議。
在NGUI的優化方面,UIPanel.LateUpdate為性能優化的重中之重,它是NGUI中CPU開銷最大的函數,沒有之一。UI模塊製作的難點並不在於其表現上,因為UI界面的表現力是由設計師來決定的,但兩套表現完全一致的UI系統,其底層的性能開銷則可能千差萬別。如何讓UI系統使用儘可能小的CPU開銷來達到設計師所設計的表現力,則足以考驗每一位UI開發人員的製作功底。
目前,我們在UWA測評報告中將統計意義上CPU開銷最為耗時的幾個函數進行展示,並將其詳細的CPU佔用和堆內存分配進行統計,從而讓研發團隊對UI系統的性能開銷進行直接地掌握,同時結合項目截圖對UI模塊何時存在較大開銷進行直觀地定位。
對於UIPanel.LateUpdate的優化,主要著眼於UIPanel的布局,其原則如下:
- 儘可能將動態UI元素和靜態UI元素分離到不同的UIPanel中(UI的重建以UIPanel為單位),從而儘可能將因為變動的UI元素引起的重構控制在較小的範圍內;
- 儘可能讓動態UI元素按照同步性進行劃分,即運動頻率不同的UI元素儘可能分離放在不同的UIPanel中;
- 控制同一個UIPanel中動態UI元素的數量,數量越多,所創建的Mesh越大,從而使得重構的開銷顯著增加。比如,戰鬥過程中的HUD運動血條可能會出現較多,此時,建議研發團隊將運動血條分離成不同的UIPanel,每組UIPanel下5~10個動態UI為宜。這種做法,其本質是從概率上儘可能降低單幀中UIPanel的重建開銷。
另外,限於篇幅限制,我們在此僅介紹NGUI中重要性能問題,而對於UGUI系統以及UI系統自身的Draw Call問題,我們將在後續的UI模塊技術專題中進行詳細的講解,敬請期待。
三、載入模塊
載入模塊同樣也是任何遊戲項目中所不可缺少的組成成分。與之前兩個模塊不同的是,載入模塊的性能開銷比較集中,主要出現於場景切換處,且CPU佔用峰值均較高。
這裡,我們先來說說場景切換時,其性能開銷的主要體現形式。對於目前的Unity版本而言,場景切換時的主要性能開銷主要體現在兩個方面,前一場景的場景卸載和下一場景的場景載入。下面,我們就具體來說說這兩個方面的性能瓶頸:
(1)場景卸載
對於Unity引擎而言,場景卸載一般是由引擎自動完成的,即當我們調用類似Application.LoadLevel的API時,引擎即會開始對上一場景進行處理,其性能開銷主要被以下幾個部分佔據:
- Destroy引擎在切換場景時會收集未標識成「DontDestoryOnLoad」的GameObject及其Component,然後進行Destroy。同時,代碼中的OnDestory被觸發執行,這裡的性能開銷主要取決於OnDestroy回調函數中的代碼邏輯。
- Resources.UnloadUnusedAssets一般情況下,場景切換過程中,該API會被調用兩次,一次為引擎在切換場景時自動調用,另一次則為用戶手動調用(一般出現在場景載入後,用戶調用它來確保上一場景的資源被卸載乾淨)。在我們測評過的大量項目中,該API的CPU開銷主要集中在500ms~3000ms之間。其耗時開銷主要取決於場景中Asset和Object的數量,數量越多,則耗時越慢。
(2)場景載入
場景載入過程的性能開銷又可細分成以下幾個部分:
- 資源載入資源載入幾乎佔據了整個載入過程的90%時間以上,其載入效率主要取決於資源的載入方式(Resource.Load或AssetBundle載入)、載入量(紋理、網格、材質等資源數據的大小)和資源格式(紋理格式、音頻格式等)等等。不同的載入方式、不同的資源格式,其載入效率可謂千差萬別,所以我們在UWA測評報告中,特別將每種資源的具體使用情況進行展示,以幫助用戶可以立刻查找到問題資源並及時進行改正。
- Instantiate實例化在場景載入過程中,往往伴隨著大量的Instantiate實例化操作,比如UI界面實例化、角色/怪物實例化、場景建築實例化等等。在Instantiate實例化時,引擎底層會查看其相關的資源是否已經被載入,如果沒有,則會先載入其相關資源,再進行實例化,這其實是大家遇到的大多數「Instantiate耗時問題」的根本原因,這也是為什麼我們在之前的AssetBundle文章中所提倡的資源依賴關係打包並進行預載入,從而來緩解Instantiate實例化時的壓力(關於AssetBundle資源的載入,則是另一個很大的Story了,我們會在以後的AssetBundle載入技術專題中進行詳細的講解)。
另一方面,Instantiate實例化的性能開銷還體現在腳本代碼的序列化上,如果腳本中需要序列化的信息很多,則Instantiate實例化時的時間亦會很長。最直接的例子就是NGUI,其代碼中存在很多SerializedField標識,從而在實例化時帶來了較多的代碼序列化開銷。因此,在大家為代碼增加序列化信息時,這一點是需要大家時刻關注的。
以上是遊戲項目中性能開銷最大的三個模塊,當然,遊戲類型的不同、設計的不同,其他模塊仍然會有較大的CPU佔用。比如,ARPG遊戲中的動畫系統和物理系統,音樂休閑類遊戲中的音頻系統和粒子系統等。對此,我們會在後續的技術專題中進行詳細的講解,敬請期待。
四、代碼效率
邏輯代碼在一個較為複雜的遊戲項目中往往佔據較大的性能開銷。這種情況在MOBA、ARPG、MMORPG等遊戲類型中非常常見。
在項目優化過程中,我們經常會想知道,到底是哪些函數佔據了大量的CPU開銷。同時,絕大多數的項目中其性能開銷都遵循著「二八原則」,即80%的性能開銷都集中在20%的函數上。所以,我們在UWA測評報告中將項目中代碼佔用的CPU開銷進行統計,不僅可以提供代碼的總體累積CPU佔用,還可以更近一步看到函數內部的性能分配,從而幫助大家更快地定位問題函數。
當然,我們還希望可以為大家提供更多的代碼性能信息,比如函數任何一幀中更為詳細的性能分配、更為準確的截圖信息等等。這些都是我們目前正在努力研發的功能,並在後續版本中提供給大家進行使用。推薦閱讀:
※A full stack engineer never allocates from heap
※Unity3D手游《仙劍奇俠傳3D回合》性能精講
※[譯]Android UI 性能優化
※移動網路下的性能優化之省電篇
※雲上運維的架構設計與混合監控