2017年3月6日 星期一

最佳實踐 - 了解Unity效能 - 協同

作者:Ian 原文

翻譯:Kelvin Lo / 海龜


Coroutines - 協同

Coroutine 運作方式和其他腳本程式不同,大多數的程式消耗只會出現在追蹤報告裡的單一位置,在特定的回呼被執行,但是 Coroutine會出現在 CPU 報告的兩個不同地方。

所有在 Coroutine 裡的初始化程式,從方法開始到第一次的 yield,這部分的追蹤數據會出現在 Coroutine 啟動的地方,通常是 StartCoroutine 被呼叫的地方。直接從從 Unity 回呼啟動的 Coroutine(比如回傳值宣告成 IEnumerator 的 Start)則會顯示在各自的 Unity 回呼下。

所有剩下的 Coroutine程式,從第一次被繼續執行到結束,都會出現在 Unity 主迴圈下的DelayedCallManager 裡。

要知道為何會這樣,就必須了解 Coroutine 的運作方式。

Coroutine 底下有個 C# 編譯器自動產生的類別的實例, 對程式設計師來說看起來像是一個普通的方法,但在實作上我們需要這個物件在每次呼叫之間保存這個 Coroutine 的狀態。因為 Coroutine 中的區域變數(local-scope variables)在 yield 之間必須保有之前賦予的值,所以這些區域變數會提到先前所說的 C# 編譯器產生的類別,它的實例在 Coroutine 的執行結束之前會維持配置在堆積記憶體上(Heap memory)。這個物件也負責追蹤 Coroutine 的執行狀態:它會記住 Coroutine 在 yield 之後下一次該從哪邊繼續。

因此,啟動一個 Coroutine 所引起的記憶體消耗同等於一個固定的成本加上這個 Coroutine 用到的區域變數總合的大小。

啟動 Coroutine 的程式建構並呼叫這個自動生成物件上的方法,然後 Unity 的 DelayedCallManager 在 Coroutine的 yield 時給的條件(WaitForSeconds、WaitForFixedUpdate 之類的條件)滿足時再次呼叫它。由於 Coroutine通常在其他 Coroutine 之外啟動 ,這會讓它們的執行消耗拆分成上述兩個不同位置。 



上圖可以看到 DelayedCallManager 正重新呼叫幾個不同的 Coroutine,像是 PopulateCharacters、AsyncLoad 和 LoadDatabase。

如果可以,儘可能用最少 Coroutine 做最多的事,雖然巢狀 Coroutine(從 Coroutine 再產生 Coroutine)非常好維護也能維持程式簡潔,但每次使用 Coroutine 就要配置新的追蹤用物件,用越多Coroutine 代表記憶體消耗也越多。

如果 Coroutine 幾乎每一幀都要執行,也沒有用 yield 在需要長時間等待的操作上,改回用 Update 或LateUpdate 會比較好。尤其是對那種周期很長或是無窮的 Coroutine 更是如此。

要記得很重要的一點是 Coroutine「不是」執行緒(threads),Coroutine 裡的同步(Synchronous)行為仍然是在主執行緒上執行的,因此如果你的目的是降低主執行緒在 CPU 上的消耗,就跟呼叫一般方法一樣必須避免在 Coroutine 上面執行會卡住(Blocking)執行緒的操作。

Coroutine 還是最適合處理長時間的非同步操作,比如等候 HTTP 傳輸、資源載入或是檔案 I/O。


Unity最佳化 - 目錄


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

沒有留言:

張貼留言

著作人

網誌存檔