CPU方面
就目前的Unity移動游戲而言,CPU方面的性能開銷主要可歸結(jié)為兩大類:引擎模塊性能開銷和自身代碼性能開銷。其中,引擎模塊中又可細(xì)致劃分為渲染模塊、動畫模塊、物理模塊、UI模塊、粒子系統(tǒng)、加載模塊和GC調(diào)用等等。正因如此,我們在UWA測評報(bào)告中,就這些模塊進(jìn)行詳細(xì)的性能分析,以方便大家快速定位項(xiàng)目的性能瓶頸,同時,根據(jù)我們的分析和建議對問題進(jìn)行迅速排查和解決。

通過大量的性能測評數(shù)據(jù),我們發(fā)現(xiàn)渲染模塊、UI模塊和加載模塊,往往占據(jù)了游戲CPU性能開銷的Top3。
一、渲染模塊
渲染模塊可以說是任何游戲中最為消耗CPU性能的引擎模塊,因?yàn)閹缀跛械挠螒蚨茧x不開場景、物體和特效的渲染。對于渲染模塊的優(yōu)化,主要從以下兩個方面入手:
(1)降低Draw Call
Draw Call是渲染模塊優(yōu)化方面的重中之重,一般來說,Draw Call越高,則渲染模塊的CPU開銷越大。究其原因,要從底層Driver和GPU的渲染流程講起,限于篇幅我們不在這里做過多的介紹。有興趣的朋友可以查看這里,或者自行Google相關(guān)的技術(shù)文獻(xiàn)。

降低Draw Call的方法則主要是減少所渲染物體的材質(zhì)種類,并通過Draw Call Batching來減少其數(shù)量。Unity文檔對于Draw Call Batching的原理和注意事項(xiàng)有非常詳細(xì)的講解,感興趣的朋友可以直接查看?Unity官方文檔。
但是,需要注意的是,游戲性能并非Draw Call越小越好。這是因?yàn)?,決定渲染模塊性能的除了Draw Call之外,還有用于傳輸渲染數(shù)據(jù)的總線帶寬。當(dāng)我們使用Draw Call Batching將同種材質(zhì)的網(wǎng)格模型拼合在一起時,可能會造成同一時間需要傳輸?shù)臄?shù)據(jù)(Texture、VB/IB等)大大增加,以至于造成帶寬“堵塞”,在資源無法及時傳輸過去的情況下,GPU只能等待,從而反倒降低了游戲的運(yùn)行幀率。
Draw Call和總線帶寬是天平的兩端,我們需要做的是盡可能維持天平的平衡,任何一邊過高或過低,對性能來說都是無益的。
(2)簡化資源
簡化資源是非常行之有效的優(yōu)化手段。在大量的移動游戲中,其渲染資源其實(shí)是“過量”的,過量的網(wǎng)格資源、不合規(guī)的紋理資源等等。所以,我們在UWA測評報(bào)告中對資源的使用進(jìn)行了詳細(xì)的展示(每幀渲染的三角形面片數(shù)、網(wǎng)格和紋理資源的具體使用情況等),以幫助大家快速查找和完善存在問題的資源。

關(guān)于渲染模塊在CPU方面的優(yōu)化方法還有很多,比如LOD、Occlusion Culling和Culling Distance等等。我們會在后續(xù)的渲染模塊技術(shù)專題中進(jìn)行更為詳細(xì)的講解,敬請期待。
二、UI模塊
UI模塊同樣也是幾乎所有的游戲項(xiàng)目中必備的模塊。一個性能優(yōu)異的UI模塊可以將游戲的用戶體驗(yàn)再抬高一個檔次。在目前國內(nèi)的大量項(xiàng)目中,NGUI作為UI解決方案的占比仍然非常高。所以,UWA測評報(bào)告對NGUI的性能分析進(jìn)行了極大的支持,我們會根據(jù)用戶所使用的UI解決方案(UGUI或NGUI)的不同提供不同的性能分析和優(yōu)化建議。

在NGUI的優(yōu)化方面,UIPanel.LateUpdate為性能優(yōu)化的重中之重,它是NGUI中CPU開銷最大的函數(shù),沒有之一。UI模塊制作的難點(diǎn)并不在于其表現(xiàn)上,因?yàn)閁I界面的表現(xiàn)力是由設(shè)計(jì)師來決定的,但兩套表現(xiàn)完全一致的UI系統(tǒng),其底層的性能開銷則可能千差萬別。如何讓UI系統(tǒng)使用盡可能小的CPU開銷來達(dá)到設(shè)計(jì)師所設(shè)計(jì)的表現(xiàn)力,則足以考驗(yàn)每一位UI開發(fā)人員的制作功底。
目前,我們在UWA測評報(bào)告中將統(tǒng)計(jì)意義上CPU開銷最為耗時的幾個函數(shù)進(jìn)行展示,并將其詳細(xì)的CPU占用和堆內(nèi)存分配進(jìn)行統(tǒng)計(jì),從而讓研發(fā)團(tuán)隊(duì)對UI系統(tǒng)的性能開銷進(jìn)行直接地掌握,同時結(jié)合項(xiàng)目截圖對UI模塊何時存在較大開銷進(jìn)行直觀地定位。

對于UIPanel.LateUpdate的優(yōu)化,主要著眼于UIPanel的布局,其原則如下:
盡可能將動態(tài)UI元素和靜態(tài)UI元素分離到不同的UIPanel中(UI的重建以UIPanel為單位),從而盡可能將因?yàn)樽儎拥腢I元素引起的重構(gòu)控制在較小的范圍內(nèi);
盡可能讓動態(tài)UI元素按照同步性進(jìn)行劃分,即運(yùn)動頻率不同的UI元素盡可能分離放在不同的UIPanel中;
控制同一個UIPanel中動態(tài)UI元素的數(shù)量,數(shù)量越多,所創(chuàng)建的Mesh越大,從而使得重構(gòu)的開銷顯著增加。比如,戰(zhàn)斗過程中的HUD運(yùn)動血條可能會出現(xiàn)較多,此時,建議研發(fā)團(tuán)隊(duì)將運(yùn)動血條分離成不同的UIPanel,每組UIPanel下5~10個動態(tài)UI為宜。這種做法,其本質(zhì)是從概率上盡可能降低單幀中UIPanel的重建開銷。
另外,限于篇幅限制,我們在此僅介紹NGUI中重要性能問題,而對于UGUI系統(tǒng)以及UI系統(tǒng)自身的Draw Call問題,我們將在后續(xù)的UI模塊技術(shù)專題中進(jìn)行詳細(xì)的講解,敬請期待。
三、加載模塊
加載模塊同樣也是任何游戲項(xiàng)目中所不可缺少的組成成分。與之前兩個模塊不同的是,加載模塊的性能開銷比較集中,主要出現(xiàn)于場景切換處,且CPU占用峰值均較高。
這里,我們先來說說場景切換時,其性能開銷的主要體現(xiàn)形式。對于目前的Unity版本而言,場景切換時的主要性能開銷主要體現(xiàn)在兩個方面,前一場景的場景卸載和下一場景的場景加載。下面,我們就具體來說說這兩個方面的性能瓶頸:
(1)場景卸載
對于Unity引擎而言,場景卸載一般是由引擎自動完成的,即當(dāng)我們調(diào)用類似Application.LoadLevel的API時,引擎即會開始對上一場景進(jìn)行處理,其性能開銷主要被以下幾個部分占據(jù):
Destroy
引擎在切換場景時會收集未標(biāo)識成“DontDestoryOnLoad”的GameObject及其Component,然后進(jìn)行Destroy。同時,代碼中的OnDestory被觸發(fā)執(zhí)行,這里的性能開銷主要取決于OnDestroy回調(diào)函數(shù)中的代碼邏輯。
Resources.UnloadUnusedAssets
一般情況下,場景切換過程中,該API會被調(diào)用兩次,一次為引擎在切換場景時自動調(diào)用,另一次則為用戶手動調(diào)用(一般出現(xiàn)在場景加載后,用戶調(diào)用它來確保上一場景的資源被卸載干凈)。在我們測評過的大量項(xiàng)目中,該API的CPU開銷主要集中在500ms~3000ms之間。其耗時開銷主要取決于場景中Asset和Object的數(shù)量,數(shù)量越多,則耗時越慢。

(2)場景加載
場景加載過程的性能開銷又可細(xì)分成以下幾個部分:
資源加載
資源加載幾乎占據(jù)了整個加載過程的90%時間以上,其加載效率主要取決于資源的加載方式(Resource.Load或AssetBundle加載)、加載量(紋理、網(wǎng)格、材質(zhì)等資源數(shù)據(jù)的大?。┖唾Y源格式(紋理格式、音頻格式等)等等。不同的加載方式、不同的資源格式,其加載效率可謂千差萬別,所以我們在UWA測評報(bào)告中,特別將每種資源的具體使用情況進(jìn)行展示,以幫助用戶可以立刻查找到問題資源并及時進(jìn)行改正。
Instantiate實(shí)例化
在場景加載過程中,往往伴隨著大量的Instantiate實(shí)例化操作,比如UI界面實(shí)例化、角色/怪物實(shí)例化、場景建筑實(shí)例化等等。在Instantiate實(shí)例化時,引擎底層會查看其相關(guān)的資源是否已經(jīng)被加載,如果沒有,則會先加載其相關(guān)資源,再進(jìn)行實(shí)例化,這其實(shí)是大家遇到的大多數(shù)“Instantiate耗時問題”的根本原因,這也是為什么我們在之前的AssetBundle文章中所提倡的資源依賴關(guān)系打包并進(jìn)行預(yù)加載,從而來緩解Instantiate實(shí)例化時的壓力(關(guān)于AssetBundle資源的加載,則是另一個很大的Story了,我們會在以后的AssetBundle加載技術(shù)專題中進(jìn)行詳細(xì)的講解)。

另一方面,Instantiate實(shí)例化的性能開銷還體現(xiàn)在腳本代碼的序列化上,如果腳本中需要序列化的信息很多,則Instantiate實(shí)例化時的時間亦會很長。最直接的例子就是NGUI,其代碼中存在很多SerializedField標(biāo)識,從而在實(shí)例化時帶來了較多的代碼序列化開銷。因此,在大家為代碼增加序列化信息時,這一點(diǎn)是需要大家時刻關(guān)注的。
以上是游戲項(xiàng)目中性能開銷最大的三個模塊,當(dāng)然,游戲類型的不同、設(shè)計(jì)的不同,其他模塊仍然會有較大的CPU占用。比如,ARPG游戲中的動畫系統(tǒng)和物理系統(tǒng),音樂休閑類游戲中的音頻系統(tǒng)和粒子系統(tǒng)等。對此,我們會在后續(xù)的技術(shù)專題中進(jìn)行詳細(xì)的講解,敬請期待。
四、代碼效率
邏輯代碼在一個較為復(fù)雜的游戲項(xiàng)目中往往占據(jù)較大的性能開銷。這種情況在MOBA、ARPG、MMORPG等游戲類型中非常常見。

在項(xiàng)目優(yōu)化過程中,我們經(jīng)常會想知道,到底是哪些函數(shù)占據(jù)了大量的CPU開銷。同時,絕大多數(shù)的項(xiàng)目中其性能開銷都遵循著“二八原則”,即80%的性能開銷都集中在20%的函數(shù)上。所以,我們在UWA測評報(bào)告中將項(xiàng)目中代碼占用的CPU開銷進(jìn)行統(tǒng)計(jì),不僅可以提供代碼的總體累積CPU占用,還可以更近一步看到函數(shù)內(nèi)部的性能分配,從而幫助大家更快地定位問題函數(shù)。

當(dāng)然,我們還希望可以為大家提供更多的代碼性能信息,比如函數(shù)任何一幀中更為詳細(xì)的性能分配、更為準(zhǔn)確的截圖信息等等。這些都是我們目前正在努力研發(fā)的功能,并在后續(xù)版本中提供給大家進(jìn)行使用。
