五月天青色头像情侣网名,国产亚洲av片在线观看18女人,黑人巨茎大战俄罗斯美女,扒下她的小内裤打屁股

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

特別優(yōu)化

2020-05-22 09:15 作者:unity_某某師_高錦錦  | 我要投稿

上一部分介紹了適用于所有項目的優(yōu)化,本節(jié)將詳細介紹在收集性能分析數(shù)據(jù)之前不應使用的優(yōu)化。可能的原因是這些優(yōu)化在實現(xiàn)時非常耗費精力,在提高性能的同時可能會損害代碼整潔性或可維護性,或者解決的可能僅僅是特定的范圍內(nèi)才存在的問題。

多維數(shù)組與交錯數(shù)組

如該?StackOverflow 文章所述,遍歷交錯數(shù)組通常比遍歷多維數(shù)組更高效,因為多維數(shù)組需要函數(shù)調(diào)用。

注意:

  • 聲明為?type[x][y]?則為數(shù)組的數(shù)組而與?type[x,y] 不同。

  • 使用 ILSpy 或類似工具檢查通過訪問多維數(shù)組生成的 IL 即可發(fā)現(xiàn)此情況。

在 Unity 5.3 中進行性能分析時,在三維 100x100x100 數(shù)組上進行 100 次完全順序的迭代得出了以下時間,這些值是通過 10 遍測試獲得的平均結(jié)果:

數(shù)組類型????????????????????????????????????????????總時間(100 次迭代)

一維數(shù)組????????????????????????????????????????????????????????660 ms

交錯數(shù)組????????????????????????????????????????????????????????730 ms

多維數(shù)組????????????????????????????????????????????????????????3470 ms

根據(jù)訪問多維數(shù)組與訪問一維數(shù)組的成本差異,可看出額外函數(shù)調(diào)用的成本,而根據(jù)訪問交錯數(shù)組與訪問一維數(shù)組的成本差異,可看出遍歷非緊湊內(nèi)存結(jié)構(gòu)的成本。

如上所述,額外函數(shù)調(diào)用的成本大大超過了使用非緊湊內(nèi)存結(jié)構(gòu)所帶來的成本。

如果操作對性能影響較大,建議使用一維數(shù)組。在任意其余情況下,如果需要一個具有多個維度的數(shù)組,請使用交錯數(shù)組。不應使用多維數(shù)組。

粒子系統(tǒng)池

對粒子系統(tǒng)建池時,請注意它們至少消耗 3500 字節(jié)的內(nèi)存。內(nèi)存消耗根據(jù)粒子系統(tǒng)上激活的模塊數(shù)量而增加。停用粒子系統(tǒng)時不會釋放此內(nèi)存;只有銷毀粒子系統(tǒng)時才會釋放。

從 Unity 5.3 開始,大多數(shù)粒子系統(tǒng)設置都可在運行時進行操作。對于必須匯集大量不同粒子效果的項目,將粒子系統(tǒng)的配置參數(shù)提取到數(shù)據(jù)載體類或結(jié)構(gòu)中可能更有效。

需要某種粒子效果時,“通用”粒子效果池即可提供必需的粒子效果對象。然后,可將配置數(shù)據(jù)應用于對象以實現(xiàn)期望的圖形效果。

這種方案比嘗試匯集給定場景中使用的粒子系統(tǒng)的所有可能變體和配置會更具內(nèi)存使用效率,但需要大量的工程努力才能實現(xiàn)。

更新管理器

在內(nèi)部,Unity 會跟蹤感興趣的列表中的對象的回調(diào)(例如?Update、FixedUpdate?和?LateUpdate)。這些列表以侵入式鏈接列表的形式進行維護,從而確保在固定時間進行列表更新。在啟用或禁用 MonoBehaviour 時分別會在這些列表中添加/刪除 MonoBehaviour。

雖然直接將適當?shù)幕卣{(diào)添加到需要它們的 MonoBehaviour 十分方便,但隨著回調(diào)數(shù)量的增加,這種方式將變得越來越低效。從原生代碼調(diào)用托管代碼回調(diào)有一個很小但很明顯的開銷。這會導致在調(diào)用大量每幀都執(zhí)行的方法時延長幀時間,而且在實例化包含大量 MonoBehaviour 的預制件時延長實例化時間(注意: 實例化成本歸因于調(diào)用預制件中每個組件上的 Awake 和 OnEnable 回調(diào)時產(chǎn)生的性能開銷)。

當具有每幀回調(diào)的 MonoBehaviour 數(shù)量增長到數(shù)百或數(shù)千時,刪除這些回調(diào)并將 MonoBehaviour(甚至標準 C# 對象)連接到全局管理器單例可以優(yōu)化性能。然后,全局管理器單例可將?Update、LateUpdate?和其他回調(diào)分發(fā)給感興趣的對象。這種方式的另一個好處是允許代碼在回調(diào)沒有操作的情況下巧妙地將回調(diào)取消訂閱,從而減少每幀必須調(diào)用的大量函數(shù)。

性能上最大的節(jié)約來自于消除很少執(zhí)行的回調(diào)。請考慮以下偽代碼:

void Update() {

if(!someVeryRareCondition) {?

return;?

}?

// … 某種操作 …?

}

如果大量 MonoBehaviour 具有上述類似 Update 回調(diào),則運行 Update 回調(diào)所使用的大量時間會用于原生和托管代碼域之間的切換以便執(zhí)行 MonoBehaviour之后再立即退出。如果這些類僅在?someVeryRareCondition?為 true 時訂閱了全局更新管理器 (Update Manager),隨后又取消了訂閱,則可節(jié)省代碼域切換和稀有條件評估所需的時間。

在更新管理器中使用 C# 委托

通常很容易想到使用普通的 C# 委托來實現(xiàn)這些回調(diào)。但是,C# 的委托實現(xiàn)方式適用于較低頻率的訂閱和取消訂閱以及少量的回調(diào)。每次添加或刪除回調(diào)時,C# 委托都會執(zhí)行回調(diào)列表的完整拷貝。在單個幀期間,大型回調(diào)列表或大量回調(diào)訂閱/取消訂閱會導致內(nèi)部?Delegate.Combine?方法性能消耗達到峰值。

如果頻繁發(fā)生添加/刪除操作,請考慮使用專為快速插入/刪除(而非委托)設計的數(shù)據(jù)結(jié)構(gòu)。

加載線程控制

Unity 允許開發(fā)者控制用于加載數(shù)據(jù)的后臺線程的優(yōu)先級。這一點對于嘗試在后臺將 AssetBundle 流式傳輸?shù)酱疟P時尤為重要。

主線程和圖形線程的優(yōu)先級都是?ThreadPriority.Normal;任何具有更高優(yōu)先級的線程都會搶占主線程/圖形線程的資源并導致幀率不穩(wěn),而優(yōu)先級較低的線程則不會。如果任何線程與主線程具有相同的優(yōu)先級,則 CPU 會嘗試為這些線程提供相同的時間,在多個后臺線程執(zhí)行繁重操作(例如 AssetBundle 解壓縮)的情況下,這通常會導致幀率卡頓。

目前,可在三個位置控制該優(yōu)先級。

首先,資源加載調(diào)用(如?Resources.LoadAsync?和?AssetBundle.LoadAssetAsync)的默認優(yōu)先級來自于?Application.backgroundLoadingPriority?設置。如文檔所述,此調(diào)用還限制了主線程用于集成資源的時間(注意: 大多數(shù)類型的 Unity 資源都必須“集成”到主線程上。集成期間將完成資源初始化并執(zhí)行某些線程安全操作。這包括編寫回調(diào)調(diào)用(例如 Awake 回調(diào))的腳本。請參閱“資源管理”指南以了解更多詳細信息,從而限制資源加載對幀時間的影響。

其次,每個異步資源加載操作以及每個 UnityWebRequest 請求都返回一個?AsyncOperation?對象以監(jiān)控和管理該操作。此?AsyncOperation?對象會顯示?priority?屬性,該屬性可用于調(diào)整各個操作的優(yōu)先級。

最后,WWW 對象(例如從?WWW.LoadFromCacheOrDownload?調(diào)用返回的對象)會顯示threadPriority?屬性。請務必注意,WWW 對象不會自動使用?Application.backgroundLoadingPriority?設置作為其默認值;WWW 對象總是被默認為?ThreadPriority.Normal。

值得注意的是,用于底層系統(tǒng)在處理解壓縮和加載數(shù)據(jù)時,不同 API 之間存在差異。Resources.LoadAsync?和?AssetBundle.LoadAssetAsync?由 Unity 的內(nèi)部 PreloadManager 系統(tǒng)進行處理,該系統(tǒng)可管理自己的加載線程并執(zhí)行自己的速率限制。UnityWebRequest?使用自己的專用線程池。WWW?在每次創(chuàng)建請求時都會生成一個全新的線程。

雖然所有其他加載機制都有內(nèi)置的排隊系統(tǒng),但 WWW 卻沒有。在大量經(jīng)過壓縮的 AssetBundle 上調(diào)用?WWW.LoadFromCacheOrDownload?會生成相同數(shù)量的線程,這些線程隨后會與主線程競爭 CPU 時間。這很容易導致幀率卡頓。

因此,使用 WWW 來加載和解壓縮 AssetBundle 時,最佳做法是為創(chuàng)建的每個 WWW 對象的?threadPriority?設置適當?shù)闹怠?/p>

大批量對象移動和 CullingGroup

正如“變換操作”部分所述,由于需要傳播更改消息,移動大型變換層級視圖的 CPU 成本相對較高。但是,在實際開發(fā)環(huán)境中,通常無法將層級視圖精簡為少量的游戲?qū)ο蟆?/p>

同時,在開發(fā)中最好僅運行那些能維持游戲世界可信度的行為,并去掉那些用戶不會注意到的行為;例如,在具有大量角色的場景中,較好的做法是僅對屏幕上的角色執(zhí)行網(wǎng)格蒙皮和動畫驅(qū)動的變換運動。對于屏幕上看不到的角色,消耗 CPU 時間來計算模擬它們的純視覺元素是種浪費。

使用 Unity 5.1 中首次引入的?CullingGroup?API 可以很好地解決這兩個問題。

不要直接操作場景中的一大群游戲?qū)ο?,應該對系統(tǒng)進行更改以操作 CullingGroup 中的一群 BoundingSphere 的 Vector3 參數(shù)。每個 BoundingSphere 充當單個游戲邏輯實體的世界空間位置的表征,并在實體移動到 CullingGroup 主攝像機的視錐體附近/內(nèi)部時接收回調(diào)。然后,可使用這些回調(diào)來激活/停用特定代碼或組件(例如 Animator),從而控制那些僅應在實體可見時才需要運行的行為。

減少方法調(diào)用開銷

C# 的字符串庫提供了一個絕佳的案例研究,其中說明了向簡單庫代碼添加額外方法調(diào)用的成本。在有關(guān)內(nèi)置字符串 API?String.StartsWith?和?String.EndsWith?的部分中,提到了手工編碼的替換比內(nèi)置方法快 10–100 倍,即使關(guān)閉了不需要的區(qū)域設置強制轉(zhuǎn)換時也是如此。

這種性能差異的主要原因僅僅是向緊湊內(nèi)循環(huán)添加額外方法調(diào)用的成本不同。調(diào)用的每個方法都必須在內(nèi)存中找到該方法的地址,并將另一個幀推入棧。所有這些操作都是有成本的,但在大多數(shù)代碼中,它們都小到可以忽略不計。

但是,在緊湊循環(huán)中運行較小的方法時,因引入額外方法調(diào)用而增加的開銷可能會變得非常顯著,甚至占主導地位。

請考慮以下兩個簡單方法。

示例 1:

int Accum { get; set; }?

Accum = 0;?

for(int i = 0; i < myList.Count; i++) {

Accum += myList[i];?

}

示例 2:

int accum = 0;?

int len = myList.Count;?

for(int i = 0; i < len; i++) {

accum += myList[i];?

}

這兩個方法都在 C# 通用?List<int>?中計算所有整數(shù)之和。第一個示例是更“現(xiàn)代的 C#”,因為它使用自動生成的屬性來保存其數(shù)據(jù)值。

雖然從表面上看這兩段代碼似乎是等效的,但通過分析代碼中的方法調(diào)用情況,可看出差異很明顯。

示例 1:

int Accum { get; set; }?

accum = 0;?

for(int i = 0; i < myList.Count; // 調(diào)用 List::getCount?

i++) {

Accum ? ? ? // 調(diào)用 set_Accum?

+= ? ? ?// 調(diào)用 get_Accum?

myList[i]; ?// 調(diào)用 List::get_Value }

每次循環(huán)執(zhí)行時都有四個方法調(diào)用:

  • myList.Count?調(diào)用?Count?屬性上的?get?方法

  • 必須調(diào)用?Accum?屬性上的?get?和?set?方法

  • 通過?get?檢索 accum?的當前值,以便將其傳遞給加法運算

  • 通過?set?將加法運算的結(jié)果分配給 accum

  • []?運算符調(diào)用列表的?get_Value?方法來檢索列表特定索引位置的項值。

示例 2:

int accum = 0;?

int len = myList.Count;?

for(int i = 0;i < len;i++) {

accum += myList[i]; // 調(diào)用 List::get_Value?

}

在第二個示例中,get_Value?調(diào)用仍然存在,但已刪除所有其他方法或不再是每個循環(huán)迭代便執(zhí)行一次。

  • 由于?accum?現(xiàn)在是原始值而不是屬性,因此不需要進行方法調(diào)用來設置或檢索其值。

  • 由于假設?myList.Count?在循環(huán)運行期間不變化,其訪問權(quán)限已移出循環(huán)的條件語句,因此不再在每次循環(huán)迭代開始時執(zhí)行它。

這兩個版本的執(zhí)行時間顯示了從這一特定代碼片段中減少 75% 方法調(diào)用開銷的真正優(yōu)勢。在現(xiàn)代臺式機上運行 100,000 次的情況下:

  • 示例 1 需要的執(zhí)行時間為 324 毫秒

  • 示例 2 需要的執(zhí)行時間為 128 毫秒

這里的主要問題是 Unity 執(zhí)行非常少的方法內(nèi)聯(lián)(即使有)。即使在 IL2CPP 下,許多方法目前也不能正確內(nèi)聯(lián)。對于屬性尤其如此。此外,虛擬方法和接口方法根本無法內(nèi)聯(lián)。

因此,在源代碼 C# 中聲明的方法調(diào)用很可能最后在最終的二進制應用程序中產(chǎn)生方法調(diào)用。

簡單屬性

為了方便開發(fā)者,Unity 為數(shù)據(jù)類型提供了許多“簡單”常量。但是,鑒于上述情況,必須注意這些常量通常作為返回常量值的屬性。

Vector3.zero 的屬性內(nèi)容如下所示:

get { return new Vector3(0,0,0); }

Quaternion.identity 非常相似:

get { return new Quaternion(0,0,0,1); }

雖然訪問這些屬性的成本與它們周圍的執(zhí)行代碼相比小的多,但它們每幀執(zhí)行數(shù)千次(或更多次)時,可產(chǎn)生一定的影響。

對于簡單的原始類型,請改用?const?值。Const?值在編譯時內(nèi)聯(lián) - 對?const?變量的引用將替換為其值。

注意:因為對?const?變量的每個引用都替換為其值,所以不建議聲明長字符串或其他大型數(shù)據(jù)類型?const。否則,由于最終二進制代碼中的所有重復數(shù)據(jù),將導致不必要地增加最終二進制文件的大小。

當?const?不適合時,應使用?static readonly?變量。在有些項目中,即使 Unity 的內(nèi)置簡單屬性也替換成了?static readonly?變量,使性能略有改善。

簡單方法

簡單方法比較棘手。如果能夠在聲明一次功能后在其他地方重用該功能,將非常有用。但是,在緊湊內(nèi)部循環(huán)中,可能有必要打破美觀編碼規(guī)則,選擇“手動內(nèi)聯(lián)”某些代碼。

有些方法可能需要徹底刪除。例如,Quaternion.Set、Transform.Translate?或?Vector3.Scale。這些方法執(zhí)行非常簡單的操作,可以用簡單的賦值語句替換。

對于更復雜的方法,應權(quán)衡手動內(nèi)聯(lián)的性能提升與維護性能更高代碼的長期成本之間的關(guān)系。


特別優(yōu)化的評論 (共 條)

分享到微博請遵守國家法律
华池县| 东宁县| 嘉义县| 云龙县| 武清区| 花莲市| 乐至县| 钦州市| 东莞市| 昌乐县| 通河县| 沐川县| 醴陵市| 绍兴市| 巨鹿县| 色达县| 乌鲁木齐县| 罗江县| 台安县| 谷城县| 横山县| 唐河县| 阿巴嘎旗| 秦安县| 汝南县| 永德县| 怀柔区| 临泽县| 延庆县| 柳河县| 安顺市| 靖宇县| 慈溪市| 六盘水市| 台北县| 肃南| 出国| 九江县| 甘洛县| 济南市| 南部县|