天天看點

Unity性能優化-GC優化

垃圾回收(GC)簡介

在GC(Garbage Collection)過程中,垃圾回收器會檢查堆(Heap)中的所有對象,搜尋它們的引用,來判斷這些對象是否還在作用域中。如果對象不在作用域中,它将被标記為需要删除。然後垃圾回收器會将這些被标記為需要删除的對象删除,收回它們所占用的記憶體空間。堆中的對象和代碼中對象引用越多,垃圾回收過程中要進行的操作就越多,其開銷也就越大。

堆記憶體空間不足、系統定時自動回收垃圾以及強制GC都會觸發垃圾回收操作。頻繁的進行堆記憶體配置設定和釋放将會導緻頻繁的GC。在Unity中,隻有 值類型局部變量 被配置設定在棧(Stack)中,它們不會引起GC,其他所有類型的資料都配置設定在堆中,由GC系統進行回收。

由GC引起的性能問題 主要表現 為:幀率低、性能時好時壞以及斷斷續續的出現卡頓。

降低由GC帶來的影響

廣泛的說,可以通過以下三種方式降低GC帶來的影響:

  • 減少GC運作所需的時間 —— 減少遊戲中的堆記憶體配置設定和對象引用數目。
  • 減少GC頻率 —— 降低對記憶體的配置設定和釋放頻率。
  • 在非性能瓶頸時期主動觸發GC —— 嘗試測算GC和堆空間擴張的時間來使其在可預測、适宜的時間發生。

減少垃圾的産生

  • 使用緩存&不要在頻繁調用的方法中配置設定堆記憶體。如果在需要頻繁調用的方法中進行了堆記憶體配置設定,并且最後舍棄了這個建立的變量,這将會産生不必要的垃圾,應該考慮在方法外建立對該變量的引用,然後重複利用它。對于無法緩存的對象,應該盡量降低方法的執行次數,僅在需要時才去執行它。
  • 使用清空集合替代建立集合。建立新的集合對象将會在堆空間配置設定記憶體。如果代碼中多次建立新的集合,應該緩存對集合的引用,在下次需要使用新的集合時,将緩存的集合清空(Clear())而不是建立全新的集合。
  • 使用對象池。如果遊戲中需要頻繁的建立再銷毀某類對象,那麼為該類對象建立對象池。

引起堆記憶體配置設定的常見原因

  • 字元串(String/string)。C#中的字元串是引用類型而不是值類型,并且它是不可變的,每次對字元串的修改,實際上是建立了一個新的字元串對象。由于字元串在程式中非常常用,是以它可能積累出較多的垃圾。遵從下面的一些簡單規則,可以保證由字元串所産生的垃圾最少: 
    • 減少不必要的字元串建立,将反複使用的字元串存儲到變量中。
    • 減少不必要的字元串修改,使用StringBuilder替換需要經常修改的字元串。
    • 當Debug.Log()不再使用後立刻将其移除,每次列印日志至少會産生一個字元串。
  • Unity方法調用。某些Unity方法的調用會進行堆記憶體配置設定,應該謹慎調用那些不是自己寫的代碼,無論它是來自Unity還是第三方插件。在Unity中,每次調用傳回數組的方法或屬性(通路器),都會建立一個新的數組再傳回,字元串也是這樣。減少此類調用或者使用替代方法,例如使用CompareTag()替換==操作。
  • 裝箱。将值類型變量當作引用類型變量使用時會進行自動裝箱。在裝箱過程中,Unity在堆記憶體中建立了一個臨時的System.Object對象,使用後該對象作為垃圾被丢棄。裝箱操作在代碼中非常常見,容易積累垃圾。一個常見的會引起裝箱的操作是将枚舉類型作為Dictionary的鍵。
  • 協程。調用StartCoroutine()方法會産生少量的垃圾,因為Unity需要建立一些類的執行個體用于管理協程。應該提前啟動那些需要在高性能要求時期運作的協程,并小心處理可能引起延遲調用的嵌套協程。協程中的yield語句本身不需要進行堆記憶體配置設定,但由它所傳回的值可能需要配置設定堆記憶體。一個常見的錯誤用法是在yield語句中使用new多次傳回相同的值,例如

    yield return new WaitForSeconds(1f);

    ,應該将WaitForSeconds(1f)對象緩存起來才對。
  • foreach循環。在比Unity 5.5更早版本的Unity中,每次foreach循環都會産生垃圾,因為該語句引發了裝箱操作。在Unity 5.5中,這個問題被修複了。如果沒辦法将Unity更新到5.5或更高版本,那麼使用for或者while替代foreach。
  • 方法引用。在Unity中,無論是對具名方法的引用還是對匿名方法的引用,都是引用類型變量,它們會引發堆記憶體配置設定。将匿名方法轉換成閉包會明顯的增加記憶體占用和堆記憶體配置設定次數。
  • LINQ和正規表達式。LINQ和正規表達式都會因引發裝箱操作而産生垃圾,最好不要使用它們。

優化代碼結構使GC帶來的影響最小化

不良的代碼結構會增加垃圾回收器的工作量。比如,讓垃圾回收器去檢查原本不需要檢查的對象。結構體(struct)是值類型的變量,但如果一個結構體中包含了引用類型的變量,那麼垃圾回收器就必須去檢查整個結構體。如果系統中有一個包含大量此類結構體的數組,那将會為垃圾回收器增加很多工作量。另一種情況就是 包含太多不必要的對象引用。當垃圾回收器為堆中的對象查找引用時,它必須在代碼中查找目前對系統中的每個對象的引用。代碼中對對象的引用越少,垃圾回收器要做的工作就越少。

手動進行強制GC

如果知道堆記憶體中有不再使用的已配置設定空間(例如加載資源所産生的垃圾)并且目前系統對性能的需求不苛刻(例如正在顯示加載畫面),則可以通過下面的代碼強制垃圾回收器進行GC:

System.GC.Collect(); 
           

繼續閱讀