2017年3月6日 星期一

最佳實踐 - 了解Unity效能 - Resources目錄和一般最佳化

作者:Ian 原文

翻譯:Kelvin Lo / 海龜

Resources 目錄 

Resources 目錄是一個常見的專案問題來源,不正確的用法會導致專案大小膨脹,記憶體用量上升,也會明顯增加程式啟動的時間。 

這些問題在官方的指南:Unity’s Guide to AssetBundles and Resources 有詳細的描述。特別是Resources 這一章值得一看。
未來也會翻譯成中文版,敬請期待。

一般最佳化

不同的效能問題需要不同的方法對症下藥,一般來說,強烈建議開發人員在開始 CPU 最佳化之前先仔細剖析專案的各項數值。但有幾個簡單的 CPU 最佳化是普遍適用的。

用 ID 來定址

Unity 內部不會用字串來定址 Animator、Material 和 Shader 屬性。為了提升速度,所有的屬性名稱都經過雜湊化(Hashed)到屬性 ID,這些 ID 就是 Unity 用來定址這些屬性的方法。

因此,每當在 Animator、Material 或 Shader 上使用 Set 或 Get 時,請使用整數參數呼叫 API 而非字串參數呼叫。字串參數 API 底下不過是再跑一次雜湊然後把雜湊值餵給整數參數 API。

從字串雜湊化所創造的 ID 在同一次執行過程中是固定的,用它們最簡單的方法是幫每個名稱宣告一個靜態唯讀(readonly)的整數變數,然後把 ID 填入。這些都會在啟動時自動初始化不需要再寫程式。

對應的 API 用在 Animator 屬性名稱是 Animator.StringToHash,用在 Material 和 Shader 屬性名稱是Shader.PropertyToID

使用不會配置記憶體的物理 API

Unity 5.3 之後的版本已導入了回傳時不會配置記憶體的物理查詢 API。用 RaycastNonAlloc 代替 RaycastAll,用 SphereCastNonAlloc 代替 SphereCastAll,依此類推,2D 物理也有對應的改正。

Transform 操作

每當 Transform 的座標或旋轉改變的時候,會觸發一個 OnTransformChanged 訊息送到該 GameObject 附帶的全部 Component 和所有子 GameObject 上。出於這個理由,最好的作法是在同一幀內設定移動和旋轉的次數儘可能的減少。把所有的改變整合後一次設定到 Transform 上。最小化 OnTransformChanged 在整個階層結構傳送的次數,這對於樹狀結構很大很深的物件(例如有骨架動畫的人物角色)尤其重要。

向量和四元數運算的順序

對於執行很多次的迴圈裡的向量和四元數計算,請記得整數(integer)運算比浮點數(floating-point)運算來的快,浮點數運算比向量和矩陣(matrix)或四元數來的快。

因此,當數學交換律(Commutative)或是結合律(Associative)允許的情況下請儘量簡化算式:

Vector3 x; int a, b; // Less efficient: results in two vector multiplications Vector3 slow = a * x * b; // More efficient: one integer mult, one vector mult Vector3 fast = a * b * x;

內建的顏色工具(ColorUtility)

先前當大家想要做 HTML 格式的色碼(#RRGGBBAA)跟 Unity 內建的 Color 或 Color32 結構轉換時很多人會用 Unify Community 維基上面的轉換腳本,這腳本又慢又占記憶體空間。

從 Unity 5 開始,裡面有一個內建的 ColorUtility API 可以有效的執行這些轉換,建議使用。

Find 和 FindObjectOfType



最好的方法是整個專案遊戲本體程式裡完全不要用 Object.Find 和 Object.FindObjectOfType。由於這些 API 會對 Unity 記憶體中所有的 GameObject 和元件進行迭代,因此隨著專案變大這些 API 效能就會越來越下降。

但有個例外是 Singleton 用來取得實體的屬性,通常有個全域 Singleton 管理物件會開放一個叫做 instance 或是之類的屬性,然後在屬性裡面可以呼叫 FindObjectOfType 來偵測 Singleton 的實體是否被建立過了。

class SomeSingleton { private SomeSingleton _instance; public SomeSingleton Instance { get { if(_instance == null) { _instance = FindObjectOfType<SomeSingleton>(); } if(_instnace == null) { _instance = CreateSomeSingleton(); } return _instance; } } }

雖然這種模式通常是可以接受的,但重要的是檢查程式並確保在 Singleton 物件不存在的場景呼叫存取器。如果 getter 沒有自動建立缺少的 Singleton 實體,那麼搜尋 Singleton 的程式會重複呼叫FindObjectOfType(一幀會很多次)並會導致效能不佳,這還蠻常見的。

尋找 Camera 的參考

在內部,Unity 的Camera.main 屬性會呼叫 Object.FindObjectWithTag,一個 Object.FindObject 的特殊變體。存取這個屬性並沒有比 Object.FindObjectOfType 來的好。如果要用程式找尋主鏡頭,強烈建議用下列兩個方法:

在 Start 或 OnEnable 回呼裡存取 Camera.main 並暫存其引用結果。

構造一個“Camera Manager”類別,提供別的元件 Camera 的參考或是用在依賴注入。

除錯碼和 [Conditional] 屬性

UnityEngine.Debug 紀錄 API 並不會因為切換到非開發模式就被移除,如果有呼叫也還會繼續紀錄 Log。大部分的開發者並不會在正式發佈的版本記錄 Log,因此建議自訂一個紀錄方法然後設定成只有在開發中會呼叫,像這樣:

public static class Logger { [Conditional("ENABLE_LOGS")] public void Debug(string logMsg) { Debug.Log(logMsg); } }

透過使用 [Conditional] 屬性來修飾這些方法,加入一個以上的參數來控制編譯時是否編譯這個方法。

如果 Conditional 屬性的所有參數都沒有在專案設定裡被定義,這個方法還有所有呼叫這個方法的程式碼在編譯時都會被剔除。它的效果和把方法放在 #if……#endif 很類似。

更多關於 Conditional 屬性的資料可以參閱 MSDN


Unity最佳化 - 目錄


  1. 分析
  2. 記憶體
  3. 協同
  4. 資源審查
  5. GC和Managed Heap
  6. 字串和Text
  7. Resources目錄和一般最佳化
  8. 特別最佳化

沒有留言:

張貼留言

著作人

網誌存檔