性能
優化是項目開發中一個永恒的話題。使用者的需求和項目的要求總在不停地增長,同屏人數、螢幕特效和場景複雜度永遠在向着“榨幹”硬體的趨勢逼近。是以,無論硬體裝置發展到何種程度、研發團隊有多麼豐富的經驗積累,性能優化依舊是一個令人棘手卻又難以規避的問題。
項目的性能優化主要圍繞CPU、GPU和記憶體三大方面進行。今天,我們就這三方面來談談目前普遍存在的性能問題和相應的解決方案。此文為第一篇:CPU專講,
同時如果各位在UWA上已經送出了性能測試,請結合報告閱讀本文,效果更佳通過大量的性能測評資料,我們發現
渲染子產品、
UI子產品和
加載子產品往往占據了遊戲CPU性能開銷的Top3。
渲染子產品可以說是任何項目中最為消耗CPU性能的引擎子產品。幾乎所有的遊戲或VR項目都離不開場景、角色和特效的渲染。對于渲染子產品的優化,主要從以下兩個方面入手:
● 降低Draw CallDraw Call是渲染子產品優化方面的重中之重。一般來說,Draw Call越高,則渲染子產品的CPU開銷越大。究其原因,要從底層Driver和GPU的渲染流程講起,限于篇幅我們不在這裡做過多的介紹。有興趣的朋友可以檢視以下連結中的說明(
http://stackoverflow.com/questio ... raw-calls-expensive),或者自行Google相關的技術文獻。
降低Draw Call的方法主要是減少渲染物體的材質種類,并通過Draw Call Batching來減少其數量。Unity文檔對于Draw Call Batching的原理和注意事項進行了非常詳細的講解,感興趣的朋友可檢視其官方文檔(
http://docs.unity3d.com/Manual/DrawCallBatching.html)。
但是,需要注意的是,遊戲性能并非Draw Call越小越好。這是因為決定渲染子產品性能的除了Draw Call之外,還有用于傳輸渲染資料的總線帶寬。當我們使用Draw Call Batching将同種材質的網格模型拼合在一起時,可能會造成同一時間需要傳輸的資料(Texture、VB/IB等)大大增加,以至于造成帶寬“堵塞”,在資源無法及時傳輸過去的情況下,GPU隻能等待,進而反倒降低了遊戲的運作幀率。
Draw Call和總線帶寬是天平的兩端,我們需要做的是盡可能維持兩者的平衡,任何一邊過高或過低,對性能來說都是無益的。
簡化資源簡化資源是非常行之有效的優化手段。在大量的項目中,其渲染資源其實是“過量”的,如過量的網格資源、不合規的紋理資源等等。是以,我們在UWA測評報告中對資源的使用進行了詳細的展示,如每幀渲染的三角形面片數、網格和紋理資源的具體使用情況等。
我們知道,在項目中美術設計師對資源的把控可謂舉足輕重。UWA測評中特有的項目管理功能,支援項目内成員互相邀請,使得開發團隊能更高效地查找和完善存在問題的各種資源。
關于渲染子產品在CPU方面的優化方法還有很多,比如LOD、Occlusion Culling和Culling Distance等等。我們會在後續的渲染子產品專題中進行更為詳細的講解,敬請期待。
UI子產品同樣是幾乎所有項目中必備的
子產品。一個性能優異的UI子產品可以讓使用者體驗再上一個台階。在目前國内的大量項目中,NGUI作為UI解決方案的占比仍然非常高,是以UWA也對NGUI
的性能分析進行了深度的研究。我們會根據使用者使用的UI解決方案(UGUI或NGUI)提供相應的性能分析和優化建議。
在NGUI的優化方面,UIPanel.LateUpdate為性能優化的重中之重,它是NGUI中CPU開銷最大的函數,沒有之一。UI子產品制作的難點并不在于其表現上,因為UI界面的表現力是由設計師來決定的,但兩套表現完全一緻的UI系統,其底層的性能開銷則可能千差萬别。如何讓UI系統使用盡可能小的CPU開銷來達到設計師所期望的表現力,則足以考驗每一位UI開發人員的制作功底。
通過對大量的UWA測評資料進行統計,我們将NGUI中CPU開銷最為耗時的幾個函數一一展示,并提供詳細的CPU占用和堆記憶體配置設定。這樣,研發團隊既可以對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版本而言,場景切換時的性能開銷主要展現在兩個方面:前一場景的場景解除安裝和下一場景的場景加載。下面,我們就具體來說說這兩個方面的性能瓶頸:
場景解除安裝場景解除安裝一般是由引擎自動完成的,即當我們調用類似Application.LoadLevel的API時,引擎即會開始對上一場景進行處理,其性能開銷主要被以下幾個部分占據:
(1)Destroy.
引擎在切換場景時會收集未辨別成“DontDestoryOnLoad”的GameObject及其Component,然後進行Destroy。同時,
代碼中的OnDestory被觸發執行,這裡的性能開銷主要取決于OnDestroy回調函數中的代碼邏輯。
(2)Resources.UnloadUnusedAssets.
一般情況下,場景切換過程中,該API會被調用兩次,一次為引擎在切換場景時自動調用,另一次則為使用者手動調用(一般出現在場景加載後,使用者調用它來確定
上一場景的資源被解除安裝幹淨)。在我們測評過的大量項目中,該API的CPU開銷主要集中在500ms~3000ms之間。其耗時開銷主要取決于場景中
Asset和Object的數量,數量越多,CPU開銷越大。
場景加載場景加載過程的性能開銷又可細分成以下幾個部分:
(1)資源加載
。資源加載幾乎占據了整個加載過程的90%時間以上,其加載效率主要取決于資源的加載方式(Resource.Load或AssetBundle加載)、
加載量(紋理、網格、材質等資源資料的大小)和資源格式(紋理格式、音頻格式等)等等。不同的加載方式、不同的資源格式,其加載效率可謂千差萬别,是以我
們在UWA測評報告中,特别将每種資源的具體使用情況進行展示,以幫助使用者可以立刻查找到”問題資源“并及時進行改正。
(2)Instantiate執行個體化
。在場景加載過程中,往往伴随着大量的Instantiate執行個體化操作,比如UI界面執行個體化、角色/怪物執行個體化、場景建築執行個體化等等。在
Instantiate執行個體化時,引擎底層會檢視其相關的資源是否已經被加載,如果沒有,則會先加載其相關資源,再進行執行個體化。這其實是大家遇到的大多數
“Instantiate耗時問題”的根本原因,也是為什麼我們在之前的AssetBundle文章中所提倡的資源依賴關系打包并進行預加載,進而來緩解
Instantiate執行個體化時的壓力(關于AssetBundle資源的加載又是另一個很深遠的話題,我們會在以後的AssetBundle加載專題中
進行詳細的講解)。
另一方面,Instantiate實
例化的性能開銷還展現在腳本代碼的序列化上,如果腳本中需要序列化的資訊很多,則Instantiate執行個體化時的時間亦會很長。最直接的例子就是
NGUI,其代碼中存在很多SerializedField辨別,進而在執行個體化時帶來了較多的代碼序列化開銷。是以,這一點是大家在為代碼增加序列化資訊
時需要時刻關注的。
以上是遊戲或VR項目中性能開銷最大的三個子產品。當然,遊戲類型的不同、設計的不同,其他子產品仍然會有較大的CPU占用。比如,ARPG遊戲中的動畫系統和實體系統,音樂休閑類遊戲中的音頻系統和粒子系統等。對此,我們會在後續的技術專題中進行深入的講解。
代碼效率邏輯代碼在一個較為複雜的項目中往往占據較大的性能開銷。特别地,在MOBA、ARPG、MMORPG等移動遊戲類型中非常多見。
在項目優化過程中,我們經常會想知
道,到底是哪些函數占據了大量的CPU開銷。同時,絕大多數的項目中其性能開銷都遵循着“二八原則”,即80%的性能開銷都集中在20%的函數上。是以,
我們在UWA測評報告中将項目中代碼占用的CPU開銷進行統計,不僅可以提供代碼的總體累積CPU占用,還可以更近一步看到函數内部的性能配置設定,進而幫助
大家更快地定位邏輯代碼的性能瓶頸。
當然,我們還希望可以為大家提供更多資訊,比如更為具體的性能配置設定、更為準确的截圖資訊等等。這些都是我們目前正在努力研發的功能,并将在後續版本中提供給大家進行使用。