Unity移動端游戲性能優(yōu)化簡譜之 以引擎模塊為劃分的CPU耗時調(diào)優(yōu)

《Unity移動端游戲性能優(yōu)化簡譜》從Unity移動端游戲優(yōu)化的一些基礎(chǔ)討論出發(fā)因悲,例舉和分析了近幾年基于Unity開發(fā)的移動端游戲項目中最為常見的部分性能問題墙歪,并展示了如何使用UWA的性能檢測工具確定和解決這些問題瞎嬉。內(nèi)容包括了性能優(yōu)化的基本邏輯、UWA性能檢測工具和常見性能問題,希望能提供給Unity開發(fā)者更多高效的研發(fā)方法和實戰(zhàn)經(jīng)驗呜袁。

今天向大家介紹文章第三部分:以引擎模塊為劃分的CPU耗時調(diào)優(yōu)馍忽,共9小節(jié),包含了渲染模塊臭挽、UI模塊、物理模塊咬腕、動畫模塊欢峰、粒子系統(tǒng)、加載模塊涨共、邏輯代碼纽帖、Lua等多個模塊等常見的游戲CPU耗時調(diào)優(yōu)講解。

(全文長約14115字举反,預(yù)計閱讀時間約30分鐘)

文章第一部分《Unity移動端游戲性能優(yōu)化簡譜之 前言》懊直、第二部分《Unity移動端游戲性能優(yōu)化簡譜之 常見游戲內(nèi)存控制》可戳此回顧,完整內(nèi)容可前往UWA學(xué)堂查看火鼻。

1. 總覽

1.1 模塊劃分

UWA將CPU中工作內(nèi)容明確室囊、耗時占比一般較高的函數(shù)整理劃分為:渲染、UI魁索、物理波俄、動畫、粒子蛾默、加載懦铺、邏輯等模塊。但這并不意味著模塊之間的工作互相獨立毫無關(guān)聯(lián)支鸡。舉例而言冬念,渲染模塊的性能壓力勢必受到復(fù)雜的UI和粒子影響,而加載模塊的很多操作實際上都是在邏輯中調(diào)用并完成的牧挣。

劃分模塊有利于我們確認(rèn)問題急前、找到重點。與此同時瀑构,也要建立起模塊之間的關(guān)聯(lián)裆针,有助于更高效地解決問題刨摩。

1.2 耗時瓶頸

當(dāng)一個項目由于CPU端性能瓶頸而產(chǎn)生幀率偏低、卡頓明顯的現(xiàn)象時世吨,如何提煉出哪個模塊的哪個問題是造成性能瓶頸的主要問題就成了關(guān)鍵澡刹。盡管我們已經(jīng)對引擎中主要模塊做了整理,各個模塊間會出現(xiàn)的問題還是會千奇百怪不可一以概之耘婚,而且它們對CPU性能壓力的貢獻(xiàn)也不盡相同罢浇。那么我們就需要對什么樣的耗時可以認(rèn)為是潛在的性能瓶頸有準(zhǔn)確的認(rèn)知。

在移動端項目中沐祷,我們CPU端性能優(yōu)化的目標(biāo)是能夠在中低端機(jī)型上大部分時間跑滿30幀的流暢游戲過程嚷闭。為了達(dá)成這一目標(biāo),簡單做一下除法就得到我們的CPU耗時均值應(yīng)控制在33ms以下赖临。當(dāng)然胞锰,這并不意味著CPU均值已經(jīng)在33ms以下的項目就已經(jīng)把CPU耗時控制的很好了。游戲運行過程中性能壓力點是不同的兢榨,可能一系列UI界面中壓力很小嗅榕、但反過來游戲中最重要的戰(zhàn)斗場景中幀率很低、又或者是存在大量幾百毫秒甚至幾秒的卡頓色乾,而最終平均下來仍然低于33ms誊册。

為此领突,UWA認(rèn)為暖璧,在一次測試中,當(dāng)33ms及以上耗時的幀數(shù)占總幀數(shù)的10%以下時君旦,可以認(rèn)為項目CPU性能整體控制在正常范圍內(nèi)澎办。而這個占比越高,說明當(dāng)前項目的CPU性能瓶頸越嚴(yán)重金砍。

以上的討論內(nèi)容主要是圍繞著我們對CPU性能的宏觀的優(yōu)化目標(biāo)局蚀,和內(nèi)存一樣,我們?nèi)砸Y(jié)合具體模塊的具體數(shù)據(jù)來排查和解決項目中實際存在的問題恕稠。

2. 渲染模塊

圍繞渲染模塊相關(guān)優(yōu)化更全面的內(nèi)容可以參考《Unity性能優(yōu)化系列—渲染模塊》琅绅。

2.1 多線程渲染

一般情況下,在單線程渲染的流程中鹅巍,在游戲每一幀運行過程中千扶,主線程(CPU1)先執(zhí)行Update,在這里做大量的邏輯更新骆捧,例如游戲AI澎羞、碰撞檢測和動畫更新等;然后執(zhí)行Render敛苇,在這里做渲染相關(guān)的指令調(diào)用妆绞。在渲染時,主線程需要調(diào)用圖形API更新渲染狀態(tài),例如設(shè)置Shader括饶、紋理株茶、矩陣和Alpha融合等,然后再執(zhí)行DrawCall巷帝,所有的這些圖形API調(diào)用都是與驅(qū)動層交互的忌卤,而驅(qū)動層維護(hù)著所有的渲染狀態(tài),這些API的調(diào)用有可能會觸發(fā)驅(qū)動層的渲染狀態(tài)地改變楞泼,從而發(fā)生卡頓驰徊。由于驅(qū)動層的狀態(tài)對于上層調(diào)用是透明的,因此卡頓是否會發(fā)生以及卡頓發(fā)生的時間長短對于API的調(diào)用者(CPU1)來說都是未知的堕阔。而此時其它CPU有可能處于空閑等待的狀態(tài)棍厂,從而造成浪費。因此可以將渲染部分抽離出來超陆,放到其它的CPU中牺弹,形成單獨的渲染線程,與邏輯線程同時進(jìn)行时呀,以減少主線程卡頓张漂。

其大致的實現(xiàn)流程是,在主線程中調(diào)用的圖形API被封裝成命令谨娜,提交到渲染隊列航攒,這樣就可以節(jié)省在主線程中調(diào)用圖形API的開銷,從而提高幀率趴梢;渲染線程從渲染隊列獲取渲染指令并執(zhí)行調(diào)用圖形API與驅(qū)動層交互漠畜,這部分交互耗時從主線程轉(zhuǎn)到渲染線程。

而Unity在Project Settings中支持且默認(rèn)開啟了Multithreaded Rendering坞靶,一般建議保持開啟憔狞。在UWA的大量測試數(shù)據(jù)中,還是發(fā)現(xiàn)有部分項目關(guān)閉了多線程渲染彰阴。開啟多線程渲染時瘾敢,CPU等待GPU完成工作的耗時會被統(tǒng)計到Gfx.WaitForPresent函數(shù)中,而關(guān)閉多線程渲染時這一部分耗時則被主要統(tǒng)計到Graphics.PresentAndSync中尿这。所以簇抵,項目中是否統(tǒng)計到Gfx.WaitForPresent函數(shù)耗時是判斷是否開啟了多線程渲染的一個依據(jù)。特別地妻味,在項目開發(fā)和測試階段可以考慮暫時性地關(guān)閉多線程渲染并打包測試正压,從而更直觀地反映出渲染模塊存在的性能瓶頸。

對于正常開啟了多線程渲染的項目责球,Gfx.WaitForPresent的耗時走向也有相當(dāng)?shù)膮⒖家饬x焦履。測試中局部的GPU壓力越大拓劝,CPU等待GPU完成工作的時間也就越長,Gfx.WaitForPresent的耗時也就越高嘉裤。所以郑临,當(dāng)Gfx.WaitForPresent存在數(shù)十甚至上百毫秒地持續(xù)耗時時,說明對應(yīng)場景的GPU壓力較大屑宠。

另外厢洞,根據(jù)UWA的大量項目和測試經(jīng)驗,GPU壓力過大也會使得渲染模塊CPU端的主函數(shù)耗時(Camera.Render和RenderPipelineManager.DoRenderLoop_Internal)整體相應(yīng)上升典奉。我們會在最后專門討論GPU部分的優(yōu)化躺翻。

2.2 同屏渲染面片數(shù)

影響渲染效率的兩個最基本的參數(shù)無疑就是Triangle和DrawCall。

通常情況下卫玖,Triangle面片數(shù)和GPU渲染耗時是成正比的公你,而對于大部分項目來說,不透明Triangle數(shù)量又往往遠(yuǎn)比半透明Triangle要多假瞬,尤其需要關(guān)注陕靠。UWA一般建議在低端機(jī)型上將同屏渲染面片數(shù)控制在25萬面以內(nèi),即便是高端機(jī)也不建議超過60萬面脱茉。當(dāng)使用工具發(fā)現(xiàn)局部同屏渲染面片數(shù)過高后剪芥,可以結(jié)合Frame Debugger對重點幀的渲染物體進(jìn)行排查。

常見的優(yōu)化方案是琴许,在制作上需要嚴(yán)格控制網(wǎng)格資源的面片數(shù)税肪,尤其是一些角色和地形的模型,應(yīng)嚴(yán)格警惕數(shù)萬面及以上的網(wǎng)格虚吟;另外寸认,一個很好的方法是一通過LOD工具減少場景中的面片數(shù)——比如在低端機(jī)上使用低模签财、減少場景中相對不重要的小物件的展示——進(jìn)而降低渲染的開銷串慰。

需要指出的是,UWA工具所關(guān)注和統(tǒng)計的面片數(shù)量并不是當(dāng)前幀場景模型的面片數(shù)唱蒸,而是當(dāng)前幀所渲染的面片數(shù)邦鲫,其數(shù)值不僅與模型面片數(shù)有關(guān),也和渲染次數(shù)相關(guān)神汹,更加直觀地反映出同屏渲染面片數(shù)造成的渲染壓力庆捺。例如:場景中的網(wǎng)格模型面片數(shù)為1萬,而其使用的Shader擁有2個渲染Pass屁魏,或者有2個相機(jī)對其同時渲染;又或者使用了SSAO、Reflection等后處理效果中的一個跃洛,那么此處所顯示的Triangle數(shù)值將為2萬晨汹。所以抵碟,在低端機(jī)上應(yīng)嚴(yán)格警惕這些一下就會使同屏渲染面片數(shù)加倍的操作,即便對于高端機(jī)也應(yīng)做好權(quán)衡坏匪,三思而后用拟逮。

2.3 Batch(DrawCall)

在Unity中,我們需要區(qū)分DrawCall和Batch适滓。在一個Batch中會存在有多個DrawCall敦迄,出現(xiàn)這種情況時我們往往更關(guān)心Batch的數(shù)量,因為它才是把渲染數(shù)據(jù)提交給GPU的單位凭迹,也是我們需要優(yōu)化和控制數(shù)量的真正對象罚屋。

降低Batch的方式通常有動態(tài)合批、靜態(tài)合批嗅绸、SRP Batcher和GPU Instancing這四種沿后,圍繞Batch優(yōu)化的討論較為復(fù)雜,再寫一篇文章也不為過朽砰,所以本文不再展開來討論尖滚,但在UWA DAY 2020中我們詳細(xì)討論和分享了DrawCall與Batch的關(guān)系以及這4種Batching的使用詳解,供大家參考:《Unity移動游戲項目優(yōu)化案例分析(上)》瞧柔。

下面簡單總結(jié)靜態(tài)合批漆弄、SRP Batcher和GPU Instancing的合批條件和優(yōu)缺點。

(1)靜態(tài)合批

條件:不同Mesh造锅,只要使用相同的材質(zhì)球即可撼唾。

優(yōu)點:節(jié)省頂點信息地綁定;節(jié)省幾何信息地傳遞哥蔚;相鄰材質(zhì)相同時, 倒谷,節(jié)省材質(zhì)地傳遞。

缺點:離線合并時糙箍,若合并的Mesh中存在重復(fù)資源渤愁,則容易使得合并后包體變大;運行時合并深夯,則生成Combine Mesh的過程會造成CPU短時間峰值抖格;同樣的,若合并的Mesh中存在重復(fù)資源咕晋,則會使得合并后內(nèi)存占用變大雹拄。

(2)SRP Batcher

條件:不同Mesh,只要使用相同的Shader且變體一樣即可掌呜。

優(yōu)點:節(jié)省Uniform Buffer的寫入操作滓玖;按Shader分Batch,預(yù)先生成Uniform Buffer质蕉,Batch內(nèi)部無CPU Write势篡。

缺點:Constant Buffer(CBuffer)的顯存固定開銷损姜;不支持MaterialPropertyBlock。

(3)GPU Instancing

條件:相同的Mesh殊霞,且使用相同的材質(zhì)球摧阅。

優(yōu)點:適用于渲染同種大量怪物的需求,合批的同時能夠降低動畫模塊的耗時绷蹲。

缺點:可能存在負(fù)優(yōu)化棒卷,反而使DrawCall上升;Instancing有時候被打亂祝钢,可以自己分組用API渲染比规。

2.4 Shader.CreateGPUProgram

該API常常在渲染模塊主函數(shù)的堆棧中出現(xiàn),并造成渲染模塊中的大多數(shù)函數(shù)峰值拦英。它是Shader第一次渲染時產(chǎn)生的耗時蜒什,其耗時與渲染Shader的復(fù)雜程度相關(guān)。當(dāng)它在游戲過程中被調(diào)用并且造成較高的耗時峰值時應(yīng)引起注意疤估。

對此灾常,我們可以將Shader通過ShaderVariantCollection收集要用到的變體并進(jìn)行AssetBundle打包。在將該ShaderVariantCollection資源加載進(jìn)內(nèi)存后铃拇,通過在游戲前期場景調(diào)用ShaderVariantCollection.WarmUp來觸發(fā)Shader.CreateGPUProgram钞瀑,并將此SVC進(jìn)行緩存,從而避免在游戲運行時觸發(fā)此API的調(diào)用慷荔、避免局部的CPU高耗時雕什。

然而即便是已經(jīng)做過以上操作的項目也常會檢測到運行時偶爾的該API耗時峰值,說明存在一些“漏網(wǎng)之魚”显晶。開發(fā)者可以結(jié)合Profiler的Timeline模式贷岸,選中觸發(fā)調(diào)用Shader.CreateGPUProgram的幀來查看具體是哪些Shader觸發(fā)了該API,可以參考《一種Shader變體收集和打包編譯優(yōu)化的思路》磷雇。

2.5 Culling

絕大多數(shù)情況下偿警,Culling本身耗時并不顯眼,它的意義在于反映一些與渲染相關(guān)的問題倦春。

(1)相機(jī)數(shù)量多

當(dāng)渲染模塊主函數(shù)的堆棧中Culling耗時的占比比較高(一般項目中在10%-20%左右)户敬。

(2)場景中小物件多

Culling耗時與場景中的GameObject小物件數(shù)量的相關(guān)性比較大落剪。這種情況建議研發(fā)團(tuán)隊優(yōu)化場景制作方式 睁本,關(guān)注場景中是否存在過多小物件,導(dǎo)致Culling耗時增高忠怖∧匮撸可以考慮采用動態(tài)加載、分塊顯示凡泣,或者Culling Group枉疼、Culling Distance等方法優(yōu)化Culling的耗時皮假。

(3)Occlusion Culling

如果項目使用了多線程渲染且開啟了Occlusion Culling,通常會導(dǎo)致子線程的壓力過大而使整體Culling過高骂维。

由于Occlusion Culling需要根據(jù)場景中的物體計算遮擋關(guān)系惹资,因此開啟Occlusion Culling雖然降低了渲染消耗,其本身的性能開銷卻也是值得注意的航闺,并不一定適用于所有場景褪测。這種情況建議開發(fā)者選擇性地關(guān)閉一部分Occlusion Culling去測試一下渲染數(shù)據(jù)的整體消耗進(jìn)行對比,再決定是否需要開啟這個功能潦刃。

(4)包圍盒更新

Culling的堆棧中有時出現(xiàn)的FinalizeUpdateRendererBoundingVolumes為包圍盒更新耗時侮措。一般常見于Skinned Mesh和粒子系統(tǒng)的包圍盒更新上。如果該API出現(xiàn)很頻繁乖杠,則要通過截圖去排查此時是否有較大量的Skinned Mesh更新分扎,或者較為復(fù)雜的粒子系統(tǒng)更新。

(5)PostProcessingLayer.OnPreCull/WaterReflection.OnWillRenderObject

PostProcessLayer.OnPreCull這一方法和項目中使用的PostProcessing Stack相關(guān)胧洒∥废牛可以在PostProcessManager.cs中添加靜態(tài)變量GlobalNeedUpdateSettings,在切場景的時候通過設(shè)置PostProcessManager.GlobalNeedUpdateSettings為true來UpdateSettings卫漫。這樣就可以避免每幀都做UpdateSettings操作庵佣,從而減少一部分耗時。

WaterReflection.OnWillRenderObject則是項目中使用到的水面反射效果的相關(guān)耗時汛兜,若該項耗時較高巴粪,可以關(guān)注一下實現(xiàn)方式上是否有可優(yōu)化的空間,比如去除一些不必要的粒子粥谬、小物件等的反射渲染肛根。

3. UI模塊

在Unity引擎中,主流的UI框架有UGUI漏策、NGUI以及使用越來越多的FairyGUI派哲。本文主要從使用最多的UGUI來進(jìn)行說明。圍繞UGUI相關(guān)優(yōu)化更全面的內(nèi)容可以參考《Unity性能優(yōu)化 — UI模塊》掺喻。

3.1 UGUI EventSystem.Update

EventSystem.Update函數(shù)為UGUI的事件系統(tǒng)耗時芭届,其耗時偏高時主要關(guān)注以下兩個因素:

(1)觸發(fā)調(diào)用耗時高

作為UGUI事件系統(tǒng)的主函數(shù),該函數(shù)主要是在觸摸釋放時觸發(fā)感耙,當(dāng)本身有較高的CPU開銷時褂乍,通常都是因為調(diào)用了其它較為耗時的函數(shù)引起。因此需要通過添加Profiler.BeginSample/EndSample打點或者GOT Online服務(wù)+UWA API打點來對所觸發(fā)的邏輯進(jìn)行進(jìn)一步地檢測即硼,從而排查出具體是哪一個子函數(shù)或者代碼段造成的高耗時逃片。

(2)輪詢耗時高

所有UGUI組件在創(chuàng)建時都默認(rèn)開啟了Raycast Target這一選項,實際上是為接受事件響應(yīng)做好了準(zhǔn)備只酥。事實上褥实,大部分比如Image呀狼、Text類型的UI組件是不會參與事件響應(yīng)的,但仍然會在鼠標(biāo)/手指劃過或懸停時參與輪詢损离,所以通過模擬射線檢測判斷UI組件是否被劃過或懸停哥艇,造成不必要的耗時。尤其在項目中UI組件比較多時僻澎,關(guān)閉不參與事件響應(yīng)的組件的Raycast Target設(shè)置她奥,可以有效降低EventSystem.Update()耗時。

3.2 UGUI Canvas.SendWillRenderCanvases

Canvas.SendWillRenderCanvases函數(shù)的耗時代表的是UI元素自身變化帶來的更新耗時怎棱,這是需要和Canvas.BuildBatch(見下文)的網(wǎng)格重建的耗時所區(qū)分的哩俭。

持續(xù)的高耗時往往是由于UI元素過于復(fù)雜且更新過于頻繁造成。UI元素的自身更新包括:替換圖片拳恋、文本或顏色發(fā)生變化等等凡资。UI元素發(fā)生位移、旋轉(zhuǎn)或者縮放并不會引起該函數(shù)有開銷谬运。該函數(shù)的耗時取決于UI元素發(fā)生更新的數(shù)量以及UI元素的復(fù)雜度隙赁,因此要優(yōu)化此函數(shù)的開銷通常可以從如下幾點著手:

(1)降低頻繁更新的UI元素的頻率

比如小地圖的怪物標(biāo)記梆暖、角色或者怪物的血條等伞访,可以控制邏輯在變動超過某個閾值時才更新UI的顯示,再比如技能CD效果轰驳,傷害飄字等控制隔幀更新厚掷。

(2)盡量讓復(fù)雜的UI不要發(fā)生變動

如某些字符串特別多且又使用了Rich Text、Outline或者Shadow效果的Text级解,Image Type為Tiled的Image等冒黑。這些UI元素因為頂點數(shù)量非常多,一旦更新便會有較高的耗時勤哗。如果某些效果需要使用Outline或者Shadowmap抡爹,但是卻又頻繁的變動,如飄動的傷害數(shù)字芒划,可以考慮將其做成固定的美術(shù)字冬竟,這樣頂點數(shù)量就不會翻N倍。

(3)關(guān)注Font.CacheFontForText

該函數(shù)往往會造成一些耗時峰值民逼。該API主要是生成動態(tài)字體Font Texture的開銷泵殴,在運行時突發(fā)高耗時,很有可能是一次性寫入很多新的字符缴挖,導(dǎo)致Font Texture紋理擴(kuò)容袋狞。可以從減少字體種類映屋、減少字體字號苟鸯、提前顯示常用字以擴(kuò)充動態(tài)字體FontTexture等方式去優(yōu)化這一項的耗時。

3.3 UGUI Canvas.BuildBatch

Canvas.BuildBatch為UI元素合并的Mesh需要改變時所產(chǎn)生的調(diào)用棚点。通常之前所提到的Canvas.SendWillRenderCanvases()的調(diào)用都會引起Canvas.BuildBatch的調(diào)用早处。另外,Canvas中的UI元素發(fā)生移動也會引起Canvas.BuildBatch的調(diào)用。

Canvas.BuildBatch是在主線程發(fā)起UI網(wǎng)格合并,具體的合并過程是在子線程中處理的号坡,當(dāng)子線程壓力過大度硝,或者合并的UI網(wǎng)格過于復(fù)雜的時候,會在主線程產(chǎn)生等待二跋,等待的耗時會被統(tǒng)計到EmitWorldScreenspaceCameraGeometry中。

這兩個函數(shù)產(chǎn)生高耗時,說明發(fā)生重建的Canvas非常復(fù)雜烂瘫,此時需要將Canvas進(jìn)行細(xì)分處理,通常是將靜態(tài)的元素放在一個Canvas中奇适,將發(fā)生更新的UI元素放入一個Canvas中坟比,這樣靜態(tài)的Canvas由于緩存不會發(fā)生網(wǎng)格更新,從而降低網(wǎng)格更新的復(fù)雜度嚷往,減少網(wǎng)格重建的耗時葛账。

3.4 UGUI CanvasRenderer.SyncTransform

我們常注意到有些項目的部分幀中CanvasRenderer.SyncTransform調(diào)用頻繁。如下圖皮仁,CanvasRenderer.SyncTransform調(diào)用次數(shù)多達(dá)1017次籍琳。當(dāng)Canvas.SyncTransform觸發(fā)次數(shù)非常頻繁時,會導(dǎo)致它的父節(jié)點UGUI.Rendering.UpdateBathes產(chǎn)生非常高的耗時贷祈。

在Unity 2018版本及以后的版本中巩割,Canvas下某個UI元素調(diào)用SetActive(false改成true)會導(dǎo)致該Canvas下的其它UI元素觸發(fā)SyncTransform,從而導(dǎo)致UI更新的整體開銷上升付燥,在Unity 2017的版本中只會導(dǎo)致該UI元素本身觸發(fā)SyncTransform宣谈。

所以,針對UI元素(如Image键科、Text)特別多的Canvas闻丑,需要注意是否存在一些UI元素在頻繁地SetActive,對于這種情況建議使用SetScale(0或者1)來代替SetActive(false或者true)勋颖∴挛耍或者,也可以將Canvas適當(dāng)拆分饭玲,讓需要進(jìn)行SetActive(true)操作的元素和其它元素不在一個Canvas下侥祭,就不會頻繁調(diào)用SyncTransform了。

3.5 UGUI UI DrawCall

通常戰(zhàn)斗場景中其它模塊耗時壓力大,此時UI模塊更要仔細(xì)控制性能開銷矮冬。一般而言谈宛,戰(zhàn)斗場景中的UI DrawCall控制到40-50左右為最佳。

在不減少UI元素的前提下胎署,控制DrawCall的問題吆录,其實也就是如何使得UI元素盡量合批的問題。一般的合批要求材質(zhì)相同琼牧,而在UI中卻常常會發(fā)生明明是使用同一材質(zhì)恢筝、同一圖集制作的UI元素卻無法合批的現(xiàn)象。這其實和UGUI DrawCall的計算原理有關(guān)巨坊。詳細(xì)的原理介紹可以參考UWA學(xué)堂的這篇課程《詳解UGUI DrawCall計算和Rebuild操作優(yōu)化》撬槽。

在UGUI的制作過程中,建議關(guān)注以下幾點:

(1)同一Canvas下的UI元素才能合批趾撵。不同Canvas即使Order in Layer相同也不合批侄柔,所以UI的合理規(guī)劃和制作非常重要;

(2)盡量整合并制作圖集鼓寺,從而使得不同UI元素的材質(zhì)圖集一致勋拟。圖集中的按鈕、圖標(biāo)等需要使用圖片的比較小的UI元素妈候,完全可以整合并制作圖集敢靡。當(dāng)它們密集地同時出現(xiàn)時,就有效降低了DrawCall苦银;

(3)在同一Canvas下啸胧、且材質(zhì)和圖集一致的前提下,避免層級穿插幔虏。簡單概括就是纺念,應(yīng)使得符合合批條件的UI元素的“層級深度”相同;

(4)將相關(guān)UI的Pos Z盡量統(tǒng)一設(shè)置為0想括,Z值不為0的UI元素只能與Hierarchy中相鄰元素嘗試合批陷谱,所以容易打斷合批。

(5)對于Alpha為0的Image瑟蜈,需要勾選其CanvasRender組件上的Cull Transparent Mesh選項烟逊,否則依然會產(chǎn)生DrawCall且容易打斷合批。

4. 物理模塊

圍繞物理模塊相關(guān)優(yōu)化更全面的內(nèi)容可以參考《Unity性能優(yōu)化 — 物理模塊》铺根。

4.1 Auto Simulation

在Unity 2017.4版本之后宪躯,物理模擬的設(shè)置選項Auto Simulation被開放并且默認(rèn)開啟,即項目過程中總是默認(rèn)進(jìn)行著物理模擬位迂。但在一些情況下访雪,這部分的耗時是浪費的详瑞。

判斷物理模擬耗時是否被浪費的一個標(biāo)準(zhǔn)就是Contacts數(shù)量,即游戲運行時碰撞對數(shù)量臣缀。一般來說坝橡,碰撞對的數(shù)量越多,則物理系統(tǒng)的CPU耗時越大肝陪。但在很多移動端項目中驳庭,我們都檢測到在整個游戲過程中Contacts數(shù)量始終為0刑顺。

在這種情況下氯窍,開發(fā)者可以關(guān)閉物理的自動模擬來進(jìn)行測試。如果關(guān)閉Auto Simulation并不會對游戲邏輯產(chǎn)生任何影響蹲堂,在游戲過程中依然可以進(jìn)行很好地對話狼讨、戰(zhàn)斗等,則說明可以節(jié)省這方面的耗時柒竞。同時也需要說明的是政供,如果項目需要使用射線檢測,那么在關(guān)閉Auto Simulation后需要開啟Auto Sync Transforms朽基,來保證射線檢測可以正常作用布隔。

4.2 物理更新次數(shù)

Unity物理模擬過程的主要耗時函數(shù)是在FixedUpdate中的,也就是說稼虎,當(dāng)每幀該函數(shù)調(diào)用次數(shù)越高衅檀、物理更新次數(shù)也就越頻繁,每幀的耗時也就相應(yīng)地高霎俩。

物理更新次數(shù)哀军,或者說FixedUpdate的每幀調(diào)用次數(shù),是和Unity Project Settings的Time設(shè)置中最小更新間隔(Fixed Timestep)以及最大允許時間(Maximum Allowed Timestep)相關(guān)的打却。這里我們需要先知道物理系統(tǒng)本身的特性杉适,即當(dāng)游戲上一幀卡頓時,Unity會在當(dāng)前幀非沉鳎靠前的階段連續(xù)調(diào)用N次FixedUpdate.PhysicsFixedUpdate猿推,Maximum Allowed Timestep的意義就在于限制物理更新的次數(shù)。它決定了單幀物理最大調(diào)用次數(shù)捌肴,該值越小蹬叭,單幀物理最大調(diào)用次數(shù)越少。現(xiàn)在設(shè)置這兩個值分別為20ms和100ms哭靖,那么當(dāng)某一幀耗時30ms時具垫,物理更新只會執(zhí)行1次;耗時200ms時也只會執(zhí)行5次试幽。

所以一個行之有效的方法是調(diào)整這兩個參數(shù)的設(shè)置筝蚕,尤其是控制更新次數(shù)的上限(默認(rèn)為17次卦碾,最好控制到5次以下),物理模塊的耗時就不會過高起宽;另一方面則是先優(yōu)化其它模塊的CPU耗時洲胖,當(dāng)項目運行過程中耗時過高的幀很少,則FixedUpdate也不會總是達(dá)到每幀更新次數(shù)的上限坯沪。這對于其它FixedUpdate中的函數(shù)是同理的绿映,也是基于這種原因,我們一般不建議在FixedUpdate中寫過多游戲邏輯腐晾。

4.3 Contacts

就像上面提到的叉弦,如果我們確實用到物理模擬,則一般碰撞對的數(shù)量越多藻糖,物理系統(tǒng)的CPU耗時也就越大淹冰。所以,嚴(yán)格控制碰撞對數(shù)量對于降低物理模塊耗時非常重要巨柒。

首先樱拴,很多項目中可能存在一些不必要的Rigidbody組件,在開發(fā)者不知情的地方造成了不必要的碰撞洋满,從而產(chǎn)生了耗時浪費晶乔;另外,可以檢查修改Project Settings的Physics設(shè)置中的Layer Collision Matrix牺勾,取消不必要的層之間的碰撞檢測正罢,將Contacts數(shù)量盡可能降低。

5. 動畫模塊

圍繞動畫模塊相關(guān)優(yōu)化更全面的內(nèi)容可以參考《Unity性能優(yōu)化 — 動畫模塊》禽最。

5.1 Mecanim動畫系統(tǒng)

Mechanic動畫系統(tǒng)是Unity公司從Unity 4.0之后開始引入的新版動畫系統(tǒng)(使用Animator控制動畫)腺怯,相比于Legacy的Animation控制系統(tǒng),在功能上川无,Mecanim動畫系統(tǒng)主要有以下幾點優(yōu)勢:

(1)針對人形角色提供了一套特殊的工作流呛占,包括Avatar的創(chuàng)建以及Muscles肌肉的調(diào)節(jié);

(2)動畫重定向(Retarting)的能力懦趋,可以非常方便地把一個動畫從一個角色模型應(yīng)用到其他角色模型上晾虑;

(3)提供了可視化的Animator編輯器,可以快捷預(yù)覽和創(chuàng)建動畫片段仅叫;

(4)更加方便地創(chuàng)建狀態(tài)機(jī)以及狀態(tài)之間Transition的轉(zhuǎn)換帜篇;

(5)便于操作的混合樹功能。

在性能上诫咱,對于骨骼動畫且曲線較多的動畫笙隙,使用Animator的性能是要比Animation要好的,因為Animator是支持多線程計算的坎缭,而且Animator可以通過開啟Optimized GameObjects進(jìn)行優(yōu)化竟痰,具體細(xì)節(jié)可以參考UWA學(xué)堂的課程《Unity移動游戲中動畫系統(tǒng)的性能優(yōu)化》签钩。相反,對于比較簡單的類似于移動旋轉(zhuǎn)這樣的動畫坏快,使用Animation控制則比Animator要高效一些铅檩。

5.2 BakeMesh

對于一兩千面這樣面數(shù)較少且動畫時長較短的對象,如MOBA莽鸿、SLG中的小兵等昧旨,可考慮用SkinnedMeshRenderer.BakeMesh的方案,用內(nèi)存換CPU耗時祥得。其原理是將一個蒙皮動畫的某個時間點上的動作兔沃,Bake成一個不帶蒙皮的Mesh,從而可以通過自定義的采樣間隔啃沪,將一段動畫轉(zhuǎn)成一組Mesh序列幀粘拾。而后在播放動畫時只需選擇最近的采樣點(即一個Mesh)進(jìn)行賦值即可窄锅,從而省去了骨骼更新與蒙皮計算的時間(幾乎沒有動畫创千,只是賦值的動作)。整個操作比較適合于面片數(shù)小的人物入偷,因為此舉省去了蒙皮計算追驴。其作用在于:用內(nèi)存換取計算時間,在場景中大量出現(xiàn)同一個帶動畫的模型時疏之,效果會非常明顯殿雪。該方法的缺點是內(nèi)存的占用極大地受到模型頂點數(shù)、動畫總時長及采樣間隔的限制锋爪。因此丙曙,該方法只適用于頂點數(shù)較少,且動畫總時長較短的模型其骄。同時亏镰,Bake的時間較長,需要在加載場景時完成拯爽。

5.3 Active Animator數(shù)量

Active狀態(tài)的Animator個數(shù)會極大地影響動畫模塊的耗時,而且是一個可量化的重要標(biāo)準(zhǔn),控制其數(shù)量到一個相對合理的值是我們優(yōu)化動畫模塊的重要手段葡粒。需要開發(fā)者結(jié)合畫面排查對應(yīng)的數(shù)量是否合理肃弟。

(1)Animator Culling Mode

控制Active Animator的一個方法是針對每個動畫組件調(diào)整合理的Animator.CullingMode設(shè)置。該項設(shè)置一共有三個選項:AlwaysAnimate桃煎、CullUpdateTransforms和CullComplete篮幢。

默認(rèn)的AlwaysAnimate使得當(dāng)前物體不管是不是在視域體內(nèi),或者在視域體被LOD Culling掉了为迈,Animator的所有東西都仍然更新三椿;其中奈揍,UI動畫一定要選AlwaysAnimate,不然會出現(xiàn)異常表現(xiàn)赋续。

而設(shè)置為CullUpdateTransforms時男翰,當(dāng)物體不在視域體內(nèi),或者被LOD Culling掉后纽乱,邏輯繼續(xù)更新蛾绎,就表示狀態(tài)機(jī)是更新的,動畫資源中連線的條件等等也都是會更新和判斷的鸦列;但是Retarget租冠、IK和從C++回傳Transform這些顯示層的更新就不做了。所以薯嗤,在不影響表現(xiàn)的前提下把部分動畫組件嘗試設(shè)置成CullUpdateTransforms可以節(jié)省物體不可見時動畫模塊的顯示層耗時顽爹。

最后,CullComplete就是完全不更新了骆姐,適用于場景中相對不重要的動畫效果镜粤,在低端機(jī)上需要保留顯示但可以考慮讓其靜止的物體,分級地選用該設(shè)置玻褪。

(2)DOTween插件

很多時候肉渴,UI動畫也會貢獻(xiàn)大量的Active Animator。針對一些簡單的UI動畫带射,如改變顏色同规、縮放、移動等效果窟社,UWA建議改用DOTween制作券勺。經(jīng)測試,性能比原生的UI動畫要好得多灿里。

5.4 開啟Apply Root Motion的Animator數(shù)量

在Animators.Update的堆棧中关炼,有時會看到Animator.ApplyBuiltinRootMotion占比過高,這一項通常和項目中開啟了Apply Root Motion的模型動畫相關(guān)钠四。如果其動畫不需要產(chǎn)生位移盗扒,則不必開啟此選項。

5.5 Animator.Initialize

Animator.Initialize API會在含有Animator組件的GameObject被Active和Instantiate時觸發(fā)缀去,耗時較高侣灶。因此尤其是在戰(zhàn)斗場景中不建議過于頻繁地對含有Animator的GameObject進(jìn)行Deactive/Active GameObject操作。對于頻繁實例化的角色和UI缕碎,可嘗試通過緩沖池的方式進(jìn)行處理褥影,在需要隱藏角色時,不直接Deactive角色的GameObject咏雌,而是Disable Animator組件凡怎,并把GameObject移到屏幕外校焦;在需要隱藏UI時,不直接Deactive UI對象统倒,而是將其SetScale=0并且移出屏幕的方式寨典,也不會觸發(fā)Animator.Initialize。

5.6 Meshskinning.Update和Animators.WriteJob

網(wǎng)格資源對于動畫模塊耗時的影響是十分顯著的房匆。

一方面耸成,Meshskinning.Update耗時較高時。主要因素為蒙皮網(wǎng)格的骨骼數(shù)和面片數(shù)偏高浴鸿,所以可以針對網(wǎng)格資源進(jìn)行減面和LOD分級井氢。

另一方面,默認(rèn)設(shè)置下岳链,我們經(jīng)常發(fā)現(xiàn)很多項目中角色的骨骼節(jié)點的Transform一直都是在場景中存在的花竞,這樣在Native層計算完它們的Transform后,會回傳給C#層掸哑,從而產(chǎn)生一定的耗時约急。

在場景中角色數(shù)量較多,骨骼節(jié)點的回傳會產(chǎn)生一定的開銷举户,體現(xiàn)在動畫模塊的主函數(shù)之一PreLateUpdate.DirectorUpdateAnimationEnd的Animators.WriteJob子函數(shù)上烤宙。

對此開發(fā)者可以考慮勾選FBX資源中Rig頁簽下的Optimize Game Objects設(shè)置項,將骨骼節(jié)點“隱藏”俭嘁,從而減少這部分的耗時。

5.7 GPU Skinning/Compute Skinning

特別地服猪,對于Unity引擎原生的GPU Skinning設(shè)置項(新版Unity中為Compute Skinning)供填,理論上會在一定程度上改變網(wǎng)格和動畫的更新方法以優(yōu)化對骨骼動畫的處理,但從針對移動平臺的多項測試結(jié)果來看罢猪,無論是在iOS還是安卓平臺上近她,多個Unity版本提供的GPU Skinning對性能的提升效果都不明顯,甚至存在負(fù)優(yōu)化的現(xiàn)象膳帕。在Unity的迭代中已對其逐步優(yōu)化粘捎,將相關(guān)操作放到渲染線程中進(jìn)行,但其實用性還需要進(jìn)一步考察危彩。

對于大量同種怪物的需求攒磨,可以考慮使用自己實現(xiàn)的《GPU Skinning 加速骨骼動畫》,和UWA開源庫中的GPU Instancing來進(jìn)行渲染汤徽,這樣既可以降低Animator.Update耗時娩缰,又能達(dá)到合批的效果。

6. 粒子系統(tǒng)

圍繞粒子系統(tǒng)相關(guān)優(yōu)化更全面的內(nèi)容可以參考《粒子系統(tǒng)優(yōu)化——如何優(yōu)化你的技能特效》谒府。

6.1 Playing粒子系統(tǒng)數(shù)量

UWA統(tǒng)計了粒子系統(tǒng)數(shù)量和Playing狀態(tài)的粒子系統(tǒng)數(shù)量拼坎。前者是指內(nèi)存中所有的ParticleSystem的總數(shù)量浮毯,包含正在播放的和處于緩存池中的;后者指的是正在播放的ParticleSystem組件的數(shù)量泰鸡,這個包含了屏幕內(nèi)和屏幕外的债蓝,我們建議在一幀中出現(xiàn)的數(shù)量峰值不超過50(1GB機(jī)型)。

針對這兩個數(shù)值盛龄,我們一方面關(guān)注粒子系統(tǒng)數(shù)量峰值是否偏高惦蚊,可選中某一峰值幀查看到底是哪些粒子系統(tǒng)緩存著、是否都合理讯嫂、是否有過度緩存的現(xiàn)象蹦锋;另一方面關(guān)注Playing數(shù)量峰值是否偏高,可選中某一峰值幀查看到底是哪些粒子系統(tǒng)在播放欧芽、是否都合理莉掂、是否能做些制作上的優(yōu)化(具體見下文GPU部分中的討論)。

6.2 Prewarm

ParticleSystem.Prewarm的耗時有時也需要關(guān)注千扔。當(dāng)有粒子系統(tǒng)開啟了Prewarm選項憎妙,其在場景中實例化或者由Deactive轉(zhuǎn)為Active時,會立即執(zhí)行一次完整的模擬曲楚。

但Prewarm的操作通常都有一定的耗時厘唾,經(jīng)測試,大量開啟Prewarm的粒子系統(tǒng)同時SetActive時會造成耗時峰值龙誊。建議在不必要的情況下抚垃,將其關(guān)閉。

7. 加載模塊

圍繞加載模塊相關(guān)優(yōu)化更全面的內(nèi)容可以參考《Unity性能優(yōu)化系列—加載與資源管理》趟大。

7.1 Shader加載

(1)Shader.Parse

Shader.Parse是指Shader加載進(jìn)行解析的操作鹤树,如果此操作較為頻繁,通常是由于Shader的重復(fù)加載導(dǎo)致的逊朽,這里的重復(fù)可以理解為2層意思罕伯。

第一層是由于Shader的冗余導(dǎo)致的,通常是因為打包AssetBundle的時候叽讳,Shader被被動打進(jìn)了多個不同的AssetBundle中而沒有進(jìn)行依賴打包追他,這樣當(dāng)這些AssetBundle中的資源進(jìn)行加載的時候,會被動加載這些Shader岛蚤,就進(jìn)行了多次“重復(fù)的”Shader.Parse邑狸,所以同一種Shader就在內(nèi)存中有多份了,這就是冗余了灭美。

要去除這種冗余的方法也很簡單推溃,就是把這些會冗余的Shader依賴打包進(jìn)一個公共的AssetBundle包。這樣就會主動打包了,而不是被動進(jìn)入某些使用了這個Shader的包體中铁坎。如果對這個Shader進(jìn)行了主動打包蜂奸,那么其它使用了這個Shader的AssetBundle中就只會對這個Shader打出來的公共AssetBundle進(jìn)行引用,這樣在內(nèi)存中就只有一份Shader硬萍,其它用到這個Shader的時候就直接引用它扩所,而不需要多次進(jìn)行Shader.Parse了。

第二層意思是同一個Shader多次地加載卸載朴乖,沒有緩存住導(dǎo)致的祖屏。假設(shè)AssetBundle進(jìn)行了主動打包,生成了公共的AssetBundle买羞,這樣在內(nèi)存中只有這一份Shader袁勺,但是因為這個Shader加載完后(也就是Shader.Parse)沒有進(jìn)行緩存,用完馬上被卸載了畜普。下次再用到這個Shader的時候期丰,內(nèi)存里沒有這個Shader了,那就必須再重新加載進(jìn)來吃挑,這樣同樣的一個Shader加載解析了多次钝荡,就造成了多次的Shader.Parse。一般而言舶衬,經(jīng)過變體優(yōu)化以后的開發(fā)者自己寫的Shader內(nèi)存占用都不高埠通,可以統(tǒng)一在游戲開始時加載并緩存。

特別地逛犹,對于Unity內(nèi)置的Shader端辱,只要是變體數(shù)量不多的,可以放進(jìn)Project Settings中的Always Included中去圾浅,從而避免這一類Shader的冗余和重復(fù)解析掠手。

(2)Shader.CreateGPUProgram

該API也會在加載模塊主函數(shù)甚至UI模塊、邏輯代碼的堆棧中出現(xiàn)狸捕。相關(guān)的討論上文已經(jīng)涉及,優(yōu)化方法相同众雷,不再贅述灸拍。

7.2 Resources.UnloadUnusedAssets

該API會在場景切換時被Unity自動調(diào)用,一般單次調(diào)用耗時較高砾省,通常情況下不建議手動調(diào)用鸡岗。

但在部分不進(jìn)行場景切換或用Additive加載場景的項目中,不會調(diào)用該API编兄,從而使得項目整體資源數(shù)量和內(nèi)存有上升趨勢轩性。對于這種情況則可以考慮每5-10min手動調(diào)用一次。

Resources.UnloadUnusedAssets的底層運作機(jī)理是狠鸳,對于每個資源揣苏,遍歷所有Hierarchy Tree中的GameObject結(jié)點悯嗓,以及堆內(nèi)存中的對象,檢測該資源是否被某個GameObject或?qū)ο螅ńM件)所使用卸察,如果全部都沒有使用脯厨,則引擎才會認(rèn)定其為Unused資源,進(jìn)而進(jìn)行卸載操作坑质。簡單來講合武,Resources.UnloadUnusedAssets的單次耗時大致隨著((GameObject數(shù)量+Mono對象數(shù)量)*Asset數(shù)量)的乘積變大而變大。

因此涡扼,該過程極為耗時稼跳,并且場景中GameObject/Asset數(shù)量越高,堆內(nèi)存中的對象數(shù)越高吃沪,其開銷也就越大汤善。對此,我們的建議如下:

(1)Resources.UnloadAsset/AssetBundle.Unload(True)

研發(fā)團(tuán)隊可嘗試在游戲運行時巷波,通過Resources.UnloadAsset/AssetBundle.Unload(True)來去除已經(jīng)確定不再使用的某一資源萎津,這兩個API的效率很高,同時也可以降低Resources.UnloadUnusedAssets統(tǒng)一處理時的壓力抹镊,進(jìn)而減少切換場景時該API的耗時锉屈;

(2)嚴(yán)格控制場景中材質(zhì)資源和粒子系統(tǒng)的使用數(shù)量。

專門提到這兩種資源垮耳,因為在大多數(shù)項目中颈渊,雖然它們的內(nèi)存占用一般不是大頭,但往往資源數(shù)量遠(yuǎn)高于其他類型的資源终佛,很容易達(dá)到數(shù)千的數(shù)量級俊嗽,從而對單次Resources.UnloadUnusedAssets耗時有較大貢獻(xiàn)。

(3)降低駐留的堆內(nèi)存铃彰。

堆內(nèi)存中的對象數(shù)量同樣會顯著影響Resources.UnloadUnusedAssets的耗時绍豁,這在上文也已經(jīng)討論過乌逐。

7.3 加載AssetBundle

使用AssetBundle加載資源是目前移動端項目中比較普遍的做法疆虚。

而其中,應(yīng)盡量用LZ4壓縮格式打包AssetBundle痕鳍,并用LoadFromFile的方式加載邪铲。經(jīng)測試芬位,這種組合下即便是較大的AssetBundle包(包含10張1024*1024的紋理),其加載耗時也僅零點幾毫秒带到。而使用其他加載方式昧碉,如LoadFromMemory,加載耗時則上升到了數(shù)十毫秒;而使用WebRequest加載則會造成AssetBundle包的駐留內(nèi)存顯著上升被饿。

這是因為四康,LoadFromFile是一種高效的API,用于從本地存儲(如硬盤或SD卡)加載未壓縮或LZ4壓縮格式的AssetBundle锹漱。

在桌面獨立平臺箭养、控制臺和移動平臺上,API將只加載AssetBundle的頭部哥牍,并將剩余的數(shù)據(jù)留在磁盤上毕泌。AssetBundle的Objects會按需加載,比如:加載方法(例如:AssetBundle.Load)被調(diào)用或其InstanceID被間接引用的時候嗅辣。在這種情況下撼泛,不會消耗過多的內(nèi)存。

但在Editor環(huán)境下澡谭,API還是會把整個AssetBundle加載到內(nèi)存中愿题,就像讀取磁盤上的字節(jié)和使用AssetBundle.LoadFromMemoryAsync一樣。如果在Editor中對項目進(jìn)行了分析蛙奖,此API可能會導(dǎo)致在AssetBundle加載期間出現(xiàn)內(nèi)存尖峰潘酗。但這不應(yīng)影響設(shè)備上的性能,在做優(yōu)化之前雁仲,這些尖峰應(yīng)該在設(shè)備上重新再測試一遍仔夺。

要注意,這個API只針對未壓縮或LZ4壓縮格式攒砖,因為如果使用LZMA壓縮缸兔,它是針對整個生成后的數(shù)據(jù)包進(jìn)行壓縮的,所以在未解壓之前是無法拿到AssetBundle的頭信息的吹艇。

由于LoadFromMemory的加載效率相較其他的接口而言惰蜜,耗時明顯增大,因此我們不建議大規(guī)模使用受神,而且堆內(nèi)存會變大抛猖。如果確實有對AssetBundle文件加密的需求,可以考慮僅對重要的配置文件鼻听、代碼等進(jìn)行加密樟结,對紋理、網(wǎng)格等資源文件則無需進(jìn)行加密精算。因為目前市面上已經(jīng)存在一些工具可以從更底層的方式來獲取和導(dǎo)出渲染相關(guān)的資源,如紋理碎连、網(wǎng)格等灰羽,因此,對于這部分的資源加密并不是十分的必要性。

在UWA GOT Online Resource模式下的資源管理頁面中可以排查加載耗時較高的AssetBundle廉嚼,從而排查和優(yōu)化加載方式玫镐、壓縮格式、包體過大等問題怠噪,或者對反復(fù)加載的AssetBundle考慮予以緩存恐似。

7.4 加載資源

有關(guān)加載資源所造成的耗時,若加載策略比較合理傍念,則一般發(fā)生在游戲一開始和場景切換時矫夷,往往不會造成嚴(yán)重的性能瓶頸。但不排除一些情況需要予以關(guān)注憋槐,那么可以把資源加載耗時的排序作為依據(jù)進(jìn)行排查双藕。

對于單次加載耗時過高的資源,比如達(dá)到數(shù)百毫秒甚至幾秒時阳仔,就應(yīng)考察這類資源是否過于復(fù)雜忧陪,從制作上考慮予以精簡。

對于反復(fù)頻繁加載且耗時不低的資源近范,則應(yīng)該在第一次加載后予以緩存嘶摊,避免重復(fù)加載造成的開銷。

值得一提的是评矩,在Unity的異步加載中有時會出現(xiàn)每幀進(jìn)行加載所能占用的最高耗時被限制叶堆,但主線程中卻在空轉(zhuǎn)的現(xiàn)象。尤其是在切場景的時候集中進(jìn)行異步加載稚照,有時會耗費幾十甚至數(shù)十秒的時間,但其中大部分時間是被空轉(zhuǎn)浪費的上枕。這是因為控制異步加載每幀最高耗時的API Application.backgroundLoadingPriority默認(rèn)值為BelowNormal,每幀最多只加載4ms辨萍。此時一般建議把該值調(diào)為High,即最多50ms每幀返弹。

在UWA GOT Online Resource模式下的資源管理頁面中可以排查加載耗時較高的資源,從而排查和優(yōu)化加載方式义起、資源過于復(fù)雜等問題,或者對反復(fù)加載的資源考慮予以緩存默终。

7.5 實例化和銷毀

實例化同樣主要存在單個資源實例化耗時過高或某個資源反復(fù)頻繁實例化的現(xiàn)象椅棺。根據(jù)耗時多少排列后,針對疑似有問題的資源两疚,前者考慮簡化,或者可以考慮分幀操作丐巫,比如對于一個較為復(fù)雜的UI Prefab勺美,可以考慮改為先實例化顯眼的、重要的界面和按鈕励烦,而翻頁后的內(nèi)容、裝飾圖標(biāo)等再進(jìn)行實例化坛掠;后者則建立緩存池,使用顯隱操作來代替頻繁的實例化舷蒲。

在UWA GOT Online Resource模式下的資源管理頁面中可以排查實例化耗時較高的資源,從而排查和優(yōu)化資源過于復(fù)雜的問題牲平,或者對反復(fù)實例化的資源考慮予以緩存域滥。

7.6 激活和隱藏

激活和隱藏的耗時本身不高,但如果單幀的操作次數(shù)過多就需要予以關(guān)注启绰。可能出于游戲邏輯中的一些判斷和條件不夠合理渊跋,很多項目中往往會出現(xiàn)某一種資源的顯隱操作次數(shù)過多着倾,且其中SetActive(True)遠(yuǎn)比SetActive(False)次數(shù)多得多、或者反之的現(xiàn)象卡者,亦即存在大量不必要的SetActive調(diào)用。由于SetActive API會產(chǎn)生C#和Native的跨層調(diào)用崇决,所以一旦數(shù)量一多镶摘,其耗時仍然是很可觀的岳守。針對這種情況碌冶,除了應(yīng)該檢查邏輯上是否可以優(yōu)化外,還可以考慮在邏輯中建立狀態(tài)緩存扑庞,在調(diào)用該API之前先判斷資源當(dāng)前的激活狀態(tài)。相當(dāng)于使用邏輯的開銷代替該API的開銷臀规,相對耗時更低一些栅隐。

在UWA GOT Online Resource模式下的資源管理頁面中可以排查激活隱藏操作較頻繁的資源塔嬉,從而排查和優(yōu)化相關(guān)邏輯和調(diào)用租悄。

8. 邏輯代碼

邏輯代碼的CPU耗時優(yōu)化更多是結(jié)合項目實際需求泣棋、考驗程序員本人的過程潭辈,很難定量定性進(jìn)行討論把敢。不過UWA SDK中提供了方便開發(fā)者在邏輯代碼中進(jìn)行打點的API&UWA GOT Online,從而將復(fù)雜的函數(shù)拆解開技竟,在報告中排查堆棧耗時榔组、更快速地驗證優(yōu)化效果搓扯。

我們發(fā)現(xiàn)有越來越的團(tuán)隊在使用JobSystem將主線程中的部分邏輯代碼放入子線程中來進(jìn)行處理,對于可以并行運算的邏輯锨推,非常推薦將其放入到子線程中來處理公壤,這樣可以有效降低主線程CPU處理邏輯運算的壓力厦幅。

9. Lua

GOT Online Lua模式提供的分析Lua造成的CPU耗時工具可視化程度高确憨,堆棧清晰明了休弃,還提供了實用且特色的倒序調(diào)用分析功能塔猾。以下結(jié)合一個Lua報告Demo簡單介紹使用該工具分析Lua耗時的方法丈甸。

重申:Lua報告中出現(xiàn)的函數(shù)名稱格式為:函數(shù)名稱@文件名:行號老虫。

可以通過報告提供的Lua文件名/行號/函數(shù)名來定位CPU耗時的瓶頸函數(shù)和CPU耗時峰值的具體原因祈匙。Lua函數(shù)的命名格式為X@Y:Z天揖,其中X是其函數(shù)名今膊,在無法獲取時斑唬,X會變?yōu)槟J(rèn)的unknown恕刘;Y是該函數(shù)定義的文件位置褐着;Z則是該函數(shù)被定義的行號含蓉。需要注意的是,當(dāng)Lua腳本以字節(jié)碼運行時着降,該值將始終為0汁展,因此建議在測試時盡可能使用Lua源碼來運行。

(1)正序調(diào)用分析——總表(曲線圖+列表)

曲線圖:

曲線選取了選取總體Lua代碼耗時和按照耗時均值正向排序的前五個函數(shù)耗時組成耗時曲線圖,每一個數(shù)據(jù)點代表了該函數(shù)在當(dāng)前幀(橫坐標(biāo))的耗時(縱坐標(biāo))公罕,有助于定位耗時瓶頸函數(shù)楼眷。

列表:

列表默認(rèn)按照耗時均值從高到低對Lua函數(shù)進(jìn)行了排序罐柳,粗略展示了函數(shù)名张吉、總CPU耗時肮蛹、場景CPU耗時省核、耗時均值等數(shù)據(jù)气忠。通過點擊函數(shù)赋咽,可以進(jìn)入對應(yīng)的單個函數(shù)分析頁面冬耿。

(2)正序調(diào)用分析——單個函數(shù)頁(截圖+曲線圖+堆棧信息)

截圖:

項目運行時截圖與使用者選中的幀大致對應(yīng)亦镶,有助于定位問題。

曲線圖:

曲線圖包括了CPU耗時曲線圖和調(diào)用次數(shù)曲線圖;也可以使用下方條縮放曲線觀察局部耗時情況燎斩。

從曲線圖中可以觀察到:函數(shù)是否存在持續(xù)性高耗時栅表;函數(shù)是否存在短暫的大量耗時怪瓶,導(dǎo)致卡頓洗贰;某些函數(shù)單次耗時并不高敛滋,但因為被大量的調(diào)用兴革,導(dǎo)致函數(shù)總耗時較高帖旨。

函數(shù)XXXX堆棧信息 (列表):

其中解阅,可以在右上角選定列表數(shù)據(jù)的時間范圍:總體堆棧信息時述召,時間范圍為全部測試時間积暖;指定場景堆棧信息時夺刑,時間范圍為指定場景的開啟時間遍愿;指定幀堆棧信息時沼填,時間范圍為當(dāng)前在曲線圖中選中的指定幀坞笙。

列表中各項指標(biāo)含義是:總體占比,以根節(jié)點函數(shù)的總耗時為100%,當(dāng)前節(jié)點函數(shù)總耗時相對根節(jié)點函數(shù)的總耗時占比梯澜;自身占比腊徙,以根節(jié)點函數(shù)的總耗時為100%,當(dāng)前節(jié)點函數(shù)自身耗時相對根節(jié)點函數(shù)的總耗時占比民傻;總耗時漓踢,時間范圍內(nèi)執(zhí)行該函數(shù)的耗時漏隐;自身耗時青责,時間范圍內(nèi)去除子節(jié)點函數(shù)(該函數(shù)調(diào)用的函數(shù))耗時剩余的耗時扁耐;調(diào)用次數(shù)婉称,時間范圍內(nèi)該函數(shù)被調(diào)用的次數(shù)王暗;單次耗時瘫筐,總耗時/調(diào)用次數(shù)策肝,表示每次執(zhí)行該函數(shù)的平均耗時之众;顯著調(diào)用幀數(shù)棺禾,該函數(shù)自身耗時大于3ms的幀數(shù)膘婶。

(3)倒序調(diào)用分析——總表(曲線圖+列表)

曲線圖:與正序調(diào)用分析不同的是,選取了自身耗時正向排序的前五個函數(shù),每一個數(shù)據(jù)點代表了該函數(shù)在當(dāng)前幀(橫坐標(biāo))的自身耗時(縱坐標(biāo))逝段。

列表:與上同理奶躯。

(4)倒序調(diào)用分析——單個函數(shù)頁(截圖+曲線圖+堆棧信息)

函數(shù)XXXX堆棧信息 (列表):

各項指標(biāo)含義(與正序相比有所不同)變?yōu)榱耍鹤陨碚急揉谇赃x定函數(shù)的自身耗時總和為100%参淹,這條調(diào)用路徑下選定函數(shù)的自身耗時相對選定節(jié)點函數(shù)總自身耗時的占比;自身耗時开呐,時間范圍內(nèi)筐付,這條調(diào)用路徑下,選定函數(shù)自身耗時的總和;調(diào)用次數(shù)畜疾,這條調(diào)用路徑的調(diào)用次數(shù)啡捶;單次耗時瞎暑,代表這條路調(diào)用路徑下了赌,選定函數(shù)的平均耗時揍拆。

在通過以上界面定位到自身耗時較高的函數(shù)后,常見的優(yōu)化手段有:優(yōu)化該函數(shù)的函數(shù)體贮喧,減少該函數(shù)自身的耗時箱沦;定位調(diào)用次數(shù)較多的調(diào)用路徑谓形,減少調(diào)用次數(shù)寒跳。

(5)注意事項

Lua CPU耗時中暫不包括GC耗時童太;Lua 函數(shù)耗時相當(dāng)于在進(jìn)出函數(shù)時打點书释,統(tǒng)計耗時爆惧。所以如果Lua腳本運行時調(diào)用了C#函數(shù)芍耘,這部分C#函數(shù)是會被統(tǒng)計進(jìn)去的蹂风,所以需要關(guān)注和C#穿插調(diào)用的情況啤覆,盡量控制在50次以內(nèi)。


本文內(nèi)容就介紹到這里啦砸泛,更多內(nèi)容可以前往UWA學(xué)堂進(jìn)行閱讀勾栗。課程將從內(nèi)存琢融、CPU宿亡、GPU三個維度討論當(dāng)前游戲項目中經(jīng)常出現(xiàn)的一些性能問題。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末臭脓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子葫录,更是在濱河造成了極大的恐慌摔竿,老刑警劉巖袁翁,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件似枕,死亡現(xiàn)場離奇詭異冗恨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蓉驹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門疟位,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事」置铮” “怎么了虹统?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵忧便,是天一觀的道長超歌。 經(jīng)常有香客問我凝垛,道長炭分,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任荐虐,我火速辦了婚禮腕铸,結(jié)果婚禮上汽烦,老公的妹妹穿的比我還像新娘。我一直安慰自己迄薄,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布色罚。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛计福,可吹牛的內(nèi)容都是我干的说订。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼思恐,長吁一口氣:“原來是場噩夢啊……” “哼婚温!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后病梢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體觅彰,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡隧期,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年性置,在試婚紗的時候發(fā)現(xiàn)自己被綠了个榕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凰萨。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖境析,靈堂內(nèi)的尸體忽然破棺而出链沼,到底是詐尸還是另有隱情曲掰,我是刑警寧澤乱豆,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布续滋,位于F島的核電站,受9級特大地震影響湿颅,放射性物質(zhì)發(fā)生泄漏谊囚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一首懈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧顽聂,春花似錦、人聲如沸爸黄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽焰轻。三九已至胧谈,卻和暖如春客冈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工燕差, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人央串。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓质和,卻偏偏與公主長得像稚字,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子褒傅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容