[筆記] Review of Unite Europe 2017 – Squeezing Unity

這是看 Unity 官方 Youtube 頻道的影片後,留下來的筆記。

相關連結:

outline

主題大綱 – 提升 Unity 效能的技巧

  • 較不良的 Unity API
  • 資料結構
  • 行內 (inline) 方法
  • Unity UI

筆記內容

講者講在最前頭的:(唉?!)

Unity APIs you cannot trust

Particle System

  • Start、Stop 等諸多 APIs 預設會對 所有的子物件 作遞迴呼叫,假如有 100 個子物件,就會導致 100 次 GetComponent,相當傷害效能。建議永遠傳入參數 withChildren = false;如果有子系統需要控制,改用事先自行 Cache 所有粒子系統並手動控制。
  • Stop 跟 Simulate 兩個 API 在呼叫的時候都會有嚴重的 GC 問題,開發者無法自行解決。這問題在 2017.2 將會修正。

Array-returning API

  • 為了資料結構的安全考量,所有回傳 Array 的 API 方法或 getter 欄位都會 產生一份新的 copy,考量記憶體配置,這類 API 盡可能減少使用。如:GetComponents、Input.Touches。
  • Input.Touches 應該用 Input.touchCount 與 Input.GetTouch 取代。
  • Physics 底下的 xxxCastAll 也因類似原因建議避開,改用新的 NonAlloc 版本。

資料結構

  • 常見的資料結構中,Array 跟 List 適合遍歷 (iteration),Dictionary 跟 HashSet 適合做 index/key 的查詢;反過來當然不建議用 Dictionary 做遍歷。
  • 但如果需要遍歷也需要查詢時?兩個都用。同時維護兩組資料結構,以記憶體空間換取運算時間。
  • 一般比較 GameObject 是否為同一物件時,C# 會比照一般類別去比較最底層的實體配置,如果要進一步優化,可以透過 Unity 的 Instance Id 來比較。
  • Object.GetInstanceID 本身需要時間,因此這個優化手段要配合 Cached Instance Id 才會明顯。另外規模不大的比較也別做這件事了。

行內方法 (inline method)

  • 行內方法可以優化方法呼叫的時間,C# 有 AggressiveInliningAttribute 可以使用,不過目前這在 Unity 不支援 IL2CPP (筆者個人覺得既然如此還是就先別用了)
  • 同樣的理由,把不必要的 property getter/setter 封裝改成 public 成員也會有優化效果。

Unity UI

接下來的 Unity UI,佔了整個講座一半。這邊內容很多,加上把講者的話轉譯過來後,沒有特別實驗確認細節,如果謬誤麻煩告訴我。

ugui

Unity UI 相關的優化 – Rebuild

  • UI 以 Canvas 為單位,有兩個重大的運算:Draw 跟 Generate Mesh。
    • Draw 固定每個 Frame 運作一次。
    • Generate Mesh 會在底下元件有改變時運算,為優化重點。
  • Generate Mesh 的動作在 UI 稱為 Rebuild,過程大致如下:
    • 計算 Auto layout
    • 重建 所有 enable 的元件對應 mesh (即使 Alpha = 0 也會重建)
    • 準備 Batch 所需要的 Material
    • 依照深度進行排序 (所有 UI 元件都是透明渲染,所以這步驟很重要)
    • Batch Mesh
  • Rebuild 過程中,深度排序因為排序演算法的極限,子物件越多,越會以 超過線性遞增 的速度增加消耗。
  • 為了減少 Rebuild 次數,建議將 UI 切成多個 Canvas,讓動態元件跟靜態元件分開,變動頻率接近的 UI 為一組,節省部分元件的重建次數。這個動作同時也會破壞 UI 的 Batch,兩者要視情況尋求平衡。
  • 另外減少 Rebuild 次數的手段,是控制 Input 對 UI 的影響,過小的變動則過濾掉。比如放任原生的 Scroll View 運作,最後幾個 Frame 可能會只剩下 0.0001 的位移,是完全不必要的 Rebuild。

Unity UI 相關的優化 – Graphic Raycaster

  • Graphic Raycaster 每個 Canvas 都會有一個,用於接受輸入訊號,判斷該傳遞何事件給予 UI 元件。
  • 如果 UI 元件本身沒有互動的需要,可以關閉 raycast target,節省 Raycaster 檢查的元件數量。
  • World Space 的 Canvas 會有個 Event Camera 用於 Graphic Raycaster,如果這個位置留下 null,Graphic Raycaster 會每個 Frame 自動用 Camera.main 補上。
  • Camera.main 本身是透過 GameObject.FindWithTag 運作,每次呼叫都會重新執行,所以不論如何,Event Camera 不要留白。
  • Camera.main 不只在 UI,整個專案都應該盡量避免呼叫,當場景中有越多 Tagged GameObject,消耗就越大。
  • 如果整個 Canvas 都不需要有玩家互動,直接關閉 Graphic Raycaster。

Unity UI 相關的優化 – SetDirty

  • UI 的 Auto Layout 是否重新計算,取決於 LayoutElement.SetDirty 是否觸發,SetDirty 本身就會造成效能瓶頸。
  • UI LayoutGroup 每次 SetDirty 都會把所有 parent 一起處理過,有時會在一個 Frame 多執行到 7.8 次,所以巢狀 LayoutGroup 很傷效能,LayoutGroup 應該盡量避免,或用 RectTransform.Anchor 來代替。
  • Layout SetDirty 的觸發來自於元件 Enable/Disable、Reparant、Property 變動等,基本上可以當作有操作變化就會觸發。
  • Canvas 本身的 Enable/Disable 不會觸發 SetDirty,所以是用來隱藏整組 UI 的好手段,只是因為沒有關閉 GameObject 本身,所以子物件有的 Update 等方法也要自行追蹤並手動開關。
  • Animator 不論是否有 Animation 的變化,都會每個 Frame 觸發 SetDirty,所以除非確定有如此高頻率的動畫需要,應該避開使用 Animator 在 UI 上。

後記

這段講座沒有對應完整的投影片,只有其中的部分內容,但已經是滿滿的資訊量噴湧而出了,是相當值得觀看的一場講座。

其中有提到幾個可以透過呼叫、修改底層 dll 的優化,因為相對極端,就沒有筆記下來。