Garbage Collection in Unreal Engine 4

Eason Chen
NeoBards
Published in
8 min readJun 5, 2019
“ assorted-color plastic toy lot” by Rick Mason on Unsplash

Garbage Collection (GC) 是 Unreal Engine 4 (UE4) 處理記憶體的方法之一。GC會自動刪掉那些遊戲中已經“不再需要”的物件,“不再需要”的意思就是這個物件已經沒有被其他物件參考到,而 UE4 的 GC 是以 Tracing 去找出些不需要的物件。

1. UE4 中的 GC 會處理哪些物件?

GC 對象就是那些 UObject!

除了自訂的 UClass/UProperty/UFunction 之外,Blueprint 中串邏輯的 node 也都各是一個 UObject,意謂著也受到 GC 管理。

以我們內部的專案來看,光是空的地圖有 40,000 個,而遊戲正式的地圖大概則有 80,000 到 100,000 個。目前知道有 2 個方法可以知道這些 UObject 的詳細資訊:

  1. 原生 Console command: Obj list

主要是 runtime 時執行來確認 UObject 的數量,結果如下圖。

“obj list” result

2. Blueprint Stats Plugin

在開啟 plugin 後,Console command輸入 DumpBlueprintStats。這個指令則是應該在 EditTime 執行,它會列出你專案內的 BP 中使用了哪些 K2Node,多少個 Custom Event 之類的資訊。

K2Node 統計
Function call 統計
Macro 統計

2. GC 的成本是?

GC Cost = Search cost + Delete cost

UE4 的 GC 可以設定作用頻率,預設是 60 秒一次,而執行 GC 的那個 frame就會有執行的 GC Cost。

GC 執行時,需要先標記那些不可達的 UObject,然後再 delete 它們。

  • Search cost = Mark phase 和 Reachability Analysis
    Search 的成本是目前主要的負擔,雖然有 multi thread,但是 UObject 的數量太多時,遊戲就會出現明顯的卡頓。
  • Delete cost = 切斷 Reference 與執行 Object Destroy(可以分攤到不同frame,並非主要負擔)。

3. 那要如何最佳化 GC?或減少 GC 時的卡頓?

  1. 減少 BP Macro 的使用,盡量用 Function
  2. BP 轉成用 C++
  3. UE4 提供了開發者可以設定的 GC 最佳化參數
GC Optimization Settings

最佳化重點:減少 Search成本,怎樣可以找東西比較快。

  1. 減少要找的對象總量 — Cluster
  2. 跳過那些不用 GC 的對象 — Disregard Index

3.1 Project Setting and Log

預設很多功能是關閉的。但既然都要優化了,第一步就是全部都打開!

接著先 packaged 看看結果如何,可能會遇到 Assertion failed 導致遊戲開不起來。如果沒問題,就把 log 開起來看。

# define UE_GCCLUSTER_VERBOSE_LOGGING (1 && !UE_BUILD_SHIPPING)
# define PROFILE_GCConditionalBeginDestroy 1
# define PROFILE_GCConditionalBeginDestroy_byClass 1

先把這三個 defintion 開起來,可以看到比較多資訊。另外,特別注意以下的 4 種 log:

  • LogUObjectArray
  • LogUObjectAllocator
  • LogObj
  • LogGarbage

3.2 Cluster

Cluster 是以 Actor 為單位,每個 Actor 會有一個 CanBeInCluster 的變數,而Cluster 必須在 CookedData 才會運作。在做可達性檢查的時候,那些在Cluster 中但不是 Cluster Root 的 object 可以略過,因此可以加快總時間。

最有效的是種在地圖上的 StaticMesh 轉換成 InstancedMeshActor 也可以成為 Cluster。

  • 失敗案例:Cluster 失敗的 log,會告訴你失敗的原因。
LogGarbage: Warning: Object BP_HPItemSpawner_C /Game/Map/Area_F/Area_F_Gameplay.Area_F_Gameplay:PersistentLevel.BP_HPItemSpawner2 from cluster LevelActorContainer
/Game/Map/Area_F/Area_F_Gameplay.Area_F_Gameplay:PersistentLevel.ActorCluster is referencing
MaterialInstanceDynamic
/Game/Map/Area_F/Area_F_Gameplay.Area_F_Gameplay:PersistentLevel.BP_HPItemSpawner2.ProgressDecal.MaterialInstanceDynamic_0 which is not part of root set or cluster.
  • 成功案例:Cluster 建立成功的 log,會告訴你 Cluster size。
LogLevelActorContainer: Created LevelActorCluster (2) for /Game/Map/Area_F/Area_F_Environment.Area_F_Environment:PersistentLevel with 4998 objects, 0 referenced clusters and 104 mutable objects.LogObj: Display: LevelActorContainer /Game/Map/Area_F/Area_F_Environment.Area_F_Environment:PersistentLevel.ActorCluster (Index: 99720), Size 4998, ReferencedClusters: 0

3.3 Disregard Index

Disregard Index是更有效的一手,以我們的遊戲來說,才剛執行起來就有49,500 個 UObject,這些很多是 UE4 自己的 UObject,所以 GC 的時候可以略過他們。

至於這個數字是多少,UE4 有準備一個輔助設定的方法:

啟動輔助設定

根據 Epic 官方文件,只要填入了上面的設定,然後 Package 遊戲,在遊戲啟動時,就會出現 log,便能可以數字填回去。

輔助設定結果

Server 與 Client 應該要填不同數字,但目前的 Project Setting 卻只有一個欄位可填入…..

4. 最佳化後可以加快幾倍?

以我們的案例來說:

  • Client 端可以加快約 11 倍
  • Server 端可以加快約 7 倍

有一項 GC Verify 的時間消耗,但那個在 Shipping game 中不會執行

Client 端在 GC 尚未調整前的 cost 約為 22.1 ms
Client 端在 GC 調整後的 cost 約為 1.5~2.2 ms
Server 端在 GC 調整前的 cost 約為 22 ms
Server 端在 GC 調整後的 cost 約為 3.2~3.3 ms

--

--