UE4におけるloadingとGCの
Profilingと最適化手法
Epic Games Japan / Support Manager
Nori Shinoyama
4.20 version!!
4.20からの変更点
• 数ページのスライドが変更されておりますが、内容は9割一緒です。
• 変更のない箇所
– Load時間のProfiling / Optimization
• 変更のある箇所
– GCのProfilingとOptimizationの箇所
• 具体的にはP31~P35のみです。
• 変更のある場所には、右の様に強調します。 4.20で更新
アジェンダ
1. Load時間(及びパッケージサイズ)のProfilingとOptimization
2. GCのProfilingとOptimization
アジェンダ
1. Load時間(及びパッケージサイズ)のProfilingとOptimization
2. GCのProfilingとOptimization
LOAD時間の
PROFILINGとOPTIMIZATION
Loadからシーンに出るまでのフロー
Storage
(NAND Flush Memory)
Memory
1. メモリにロード
一般的に「ロード時間」
と呼ばれる部分
2. AddToWorldして
Visibleに
AddToWorld時の瞬間的なカクつき
(Level Streaming時にプレイに影響)
Load時間のProfilingと最適化手法
• Profiling
– Stat Levels
– Loadtimes.dumpreport
– 注意点: ENABLE_LOADTIME_TRACKING defineに関して
– PERF_TRACK_DETAILED_ASYNC_STATS
• Optimization
– おさらい
– FileOpenOrder
– 不要なデータの削除例
• Material: Shader Permutation Reduction
• Vertex: Reverse Index Bufferの削除
– AssetManagerによるPreloading
– AddToWorldをフレーム分散させる
– EventBeginPlayの負荷を減らす
Load時間のProfiling
Stat Levels
• 灰=パーシスタントレベル
• 赤=未ロード
• 紫=ロード中
• 橙=AddToWorld
• 緑=ロード完了
ロード時間計測用のStat
Load時間のProfiling
Loadtimes.DumpReport (Loadtimes.reset)
• LoadTimes.DumpReport
– 各種パッケージがのロード時間をリスト
• FILEとつけると.loadreportファイルにダンプ
• LOWTIME=0.05コマンドつけると、0.05s以下のパッケージはダンプされない。
Dumping all loaded assets by exclusive load time:
2158.6ms: /Game/Product/Assets/Maps/AAAAAAAAA
1201.3ms: /Game/Product/Assets/Maps/BBBBBBBBB
635.9ms: /Game/Product/Assets/Maps/CCCCCC
598.0ms: /Game/Product/Resources/Environments/StaticMeshAAAA
525.3ms: /Game/Product/Resources/Environments/StaticMeshBBBB
Dumptimes出力例
Load時間のProfiling
Loadtimes.reset
• LoadTimes.Reset
– このコマンドが計測しているロード時間などをリセットできる。
計測したいロード部分の前に、これを呼び出しておくこと。
Load時間のProfiling (備考)
整備されていないコマンドやDefineについて
• ロード時間を調査したりコードを読むと、以下のコマンドやdefine
を見つけるかと思います。
– LoadTimes.DumpTracking
– LoadTimes.DumpTrackingLow
– #define ENABLE_LOADTIME_TRACKING
– #define ENABLE_LOADTIME_RAW_TIMINGS
• これらは整備されておらず、使用しても有益な情報は得られません。
今後どうなるかはわかりませんが、現状は使わないでください
AddToWorldのProfiling
PERF_TRACK_DETAILED_ASYNC_STATS (AsyncLoading.h)
• LevelのShouldBeVisibleがONになったりなどのAddToWorldをす
る際にどれだけのコストがかかるかがでる。
例:
UWorld::AddToWorld: updating components for /Game/Sub took (less than) 110.62 ms
Detailed AddToWorld stats for '/Game/Sub' - Total 425.37ms
Move Actors : 0.00 ms
Shift Actors : 0.00 ms
Update Components : 425.12 ms
Init BSP Phys : 0.00 ms
Init Actor Phys : 0.00 ms
Init Actors : 0.00 ms
Initialize : 0.08 ms
…
Load時間のProfilingと最適化手法
• Profiling
– Stat Levels
– Loadtimes.dumpreport
– 注意点: ENABLE_LOADTIME_TRACKING defineに関して
– PERF_TRACK_DETAILED_ASYNC_STATS
• Optimization
– おさらい
– FileOpenOrder
– 不要なデータの削除例
• Material: Shader Permutation Reduction
• Vertex: Reverse Index Bufferの削除
– AssetManagerによるPreloading
– AddToWorldをフレーム分散させる
– EventBeginPlayの負荷を減らす
Load時間のOptimization
おさらい
– PAK ファイルを使う
– Compressionはコンソール次第
公式ドキュメント.pakファイルの圧縮を参考にしてください
Load時間のOptimization
FileOpenOrder
• ゲーム内で読み込み順に、Pakファイル内部のアセットをソートする機能
– 事前にプレイして、アセット読み込み順のログを取る必要がある
– やり方の詳細はOfficial Docにあります
プロジェクトのパッケージ化
• ※HDDのシーク距離を抑えるためのものです。
Load時間のProfilingと最適化手法
• Profiling
– Stat Levels
– Loadtimes.dumpreport
– 注意点: ENABLE_LOADTIME_TRACKING defineに関して
– PERF_TRACK_DETAILED_ASYNC_STATS
• Optimization
– おさらい
– FileOpenOrder
– 不要なデータの削除例
• Material: Shader Permutation Reduction
• Vertex: Reverse Index Bufferの削除
– AssetManagerによるPreloading
– EventBeginPlayの負荷を減らす
Load時間のOptimization
不要なデータの削除: Shader Permutation Reduction
• UE4.13から搭載された機能。
– プロジェクトで使わない処理を前もって指定することで、その処理用のシェー
ダを生成しない。
• マテリアルサイズ&シェーダコンパイルの時間どちらにも効果がある。
このマテリアルで検証。
(usageはstatic lightingのみ。)
全部つけたもの vs 全部消したもの
25 17
シェーダ数を32%削減
Load時間のOptimization
不要なデータの削除: Shader Permutation Reduction
• その他、Materialのデータ量削減に関しては、以下のドキュメント
で詳細を述べていますので、参考になさってください。
マテリアルとマテリアルインスタンスの仕組みと問題点の共有
Load時間のOptimization
不要なデータの削除: 不要Index Bufferらの削除
基本的にOffで良いと思うもの
Reverse Index Buffer
マイナススケールレンダリング時にコンテキストロールを削減するための機能
こちらは要検討
Depth-only Index Buffer
影描画時に使うバッファ。
こちらはOffにすることでShadowMap作成のGPUコストが若干上がる可能性あり
Adjacency Index Buffer
Tessellationのためのバッファ。使わない場合はOffに。
(Tessellation未対応のプラットフォームではこのチェックの有無に関わらずこの
バッファは生成されません)
Load時間のOptimization
AssetManagerによるPreloading
Level単位ではなくAsset単位で裏で読み込む機能があります。
【UE4】AssetManagerを使用したレベルストリームの高速化
UE4 Doc: アセット管理
メモリが許すかつロードできるアセットが事前にわかる場合、
前読みも検討できるかと思います。
AddToWorldの最適化
AddToWorldの処理を複数フレームに分散させる
• Project SettingsにLevel Streaming In(Out)時の
AddToWorld(RemoveFromWorld)を分散させる設定があります。
AddToWorld時の
1フレームの処理負荷制限
投入されるオブジェクトの単位
RemoveFromWorldの
1フレームの処理負荷制限
投入されるオブジェクトの単位
AddToWorldの最適化
EventBeginPlayの処理を減らす
• BeginPlayに高負荷な処理をしているとAddToWorldに影響
• ConstructionScriptに逃がせるものは逃がすなど
Load時間のProfilingと最適化手法
• Profiling
– Stat Levels
– Loadtimes.dumpreport
– 注意点: ENABLE_LOADTIME_TRACKING defineに関して
– PERF_TRACK_DETAILED_ASYNC_STATS
• Optimization
– おさらい
– FileOpenOrder
– 不要なデータの削除例
• Material: Shader Permutation Reduction
• Vertex: Reverse Index Bufferの削除
– AssetManagerによるPreloading
– EventBeginPlayの負荷を減らす
アジェンダ
1. Load時間(及びパッケージサイズ)のProfilingとOptimization
2. GCのProfilingとOptimization
GARBAGE COLLECTIONの
PROFILINGとOPTIMIZATION
Garbage CollectionのProfilingと最適化手法
• GCの問題点
• GCの考え方
• GCコストの計測方法
• タイトルでやることできること
GCの問題点
• GCが起きたときの急激なヒッチ
– レベルストリーミングにより。。
• 遷移時のシーンのオブジェクトの一時的な増加
• 遷移後の大量のオブジェクトの破棄
GCの考え方
UObjectArray: UObject(Uproperty)の一次元配列
UE4はUobjectすべてを一次元配列で管理
つまり、
GCのコスト = 検索コスト + 削除コスト
GCが起きると、このすべてのオブジェクトを検索し、不要なものを削除する
削除コストの詳細
GCのフレーム分散
GCのコスト = 検索コスト + 削除コスト
と記載しましたが、正確ではありません
削除コスト = 依存関係の切断 + 実際のオブジェクトの削除
GCが呼び出されたフレームのインパクト = 検索コスト + 依存関係の切断
こちらはフレーム分散可能
今日はこれを便宜上「削除コスト」と呼びます
4.20以前
削除コストの詳細
GCのフレーム分散
GCのコスト = 検索コスト + 削除コスト
と記載しましたが、正確ではありません
削除コスト = 依存関係の切断 + 実際のオブジェクトの削除
GCが呼び出されたフレームのインパクト = 検索コスト + 依存関係の切断の1フレーム目
今日はこれを便宜上「削除コスト」と呼びます
4.20で更新
どちらも分散可能に!!
つまり、
GCのコスト = 検索コスト + 削除コスト
4.20以前
つまり、
GCのコスト = 検索コスト + 削除コスト
4.20で更新
複数フレームに分散されるのでカクツキの
原因にはなりにくくなるはずです。
4.20からGCによるSweep処理のBeginDestroyが分散可能に
(defaultでON)
4.20で更新
エンジン最適化による高速化
• エンジン内部の最適化で4.16で高速化が実現
• 例:
– 検索: 40ms -> 12ms
– 削除: 70ms -> 48ms
(発展)レベルストリーミングアウトのときの
FinishDestroy(FD)のフレーム分散化
現在、レベルストリーミングアウトのとき、実際のオブジェクトの削除部分がフレームをまたいで分
散される様な設定になってはおりません。
それをするためには以下の様に修正をお願いいたします。
(UE4としてこれはデフォルト動作としないため、今後組み込まれる予定はございません。
しかし、下の修正での動作実績があり、Epic本社もこの変更を認めております。)
----
■レベルストリーミングアウト時の、FinishDestroyのインクリメンタル化
1. void UWorld::UpdateLevelStreaming()のForceGarbageCollection(true); <- 中身をfalseに。
2.UWorld* UWorld::FindWorldInPackage()のGetObjectsWithOuterを
GetObjectsWithOuter(Package, PotentialWorlds, false, EObjectFlags::RF_NoFlags,
EInternalObjectFlags::PendingKill);
に。
3. UWorld* UWorld::FollowWorldRedirectorInPackage()のGetObjectsWithOuterを
GetObjectsWithOuter(Package, PotentialRedirectors, false, EObjectFlags::RF_NoFlags,
EInternalObjectFlags::PendingKill);に。
GCコストの確認方法
GC関連コストの確認方法
1. Log loggarbage log (Log loggarbage verbose)
2. Stat dumphitches
3. (Stat Startfile/Stopfile)
4. CBD Profiling Tools
5. Obj –list / Bluerprint Stats
1/5: LogGarbage log
• GC負荷の大まかなプロファイルのメインコマンド
• “log LogGarbage log”コマンドでGC時に以下のようなログが出る。
LogGarbage: 74.040701 ms for GC
LogGarbage: 2.808510 ms for unhashing unreachable objects. Clusters removed: 0.
検索コスト
削除コスト
2/5: ヒッチ時のCPU負荷のロギング
Command: “stat dumphitches”
----------------- Game Thread 325.24ms
325.216ms ( 4) - Thread_189a1_0 - GameThread - STATGROUP_Threads
325.208ms ( 2) - FrameTime - STAT_FrameTime - STATGROUP_Engine
322.049ms ( 1) - GameEngine Tick - STAT_GameEngineTick -
316.684ms ( 1) - World Tick Time - STAT_WorldTickTime -
293.497ms ( 1) - CollectGarbageInternal -
145.057ms ( 1) - FRealtimeGC::PerformReachabilityAnalysis -
注意点:
VERIFY GC ASSUMPTIONS
GCの本来のコストを見積もるならば、
TESTビルドで検証することを強くおすすめします。
• Development Buildで基本ON (Test/Shippingでは基本無効)
• CollectGarbageInternal.VerifyGCAssumptionsが走ってGCが激重に
• なしにするには、実行時に”-NOVERIFYGC”オプションをつけて起動
3/5: Stat startfile/stopfile
4/5: 削除コスト調査用Define
# define PROFILE_GCConditionalBeginDestroy
# define PROFILE_GCConditionalBeginDestroy_byClass
LogGarbage: Collecting garbage
LogGarbage: 9.762678 ms for GC
LogGarbage: 59.374099 ms for unhashing unreachable objects. Clusters removed: 111. Items 18567
Cluster Items 735
LogTemp: 1090 cnt 2.23us per 2.43ms total /Game/Blueprints/Character/AAAAAAAAA
LogTemp: 615 cnt 2.58us per 1.59ms total /Game/Blueprints/Character/BBBBBBBBBB
LogTemp: 698 cnt 2.11us per 1.48ms total /Game/Blueprints/Character/CCCCCCCCCC
LogTemp: 489 cnt 2.64us per 1.29ms total /Game/Blueprints/Gimmick/GimmickAAAAA
LogTemp: 261 cnt 4.22us per 1.10ms total /Game/Maps/MAPMAPMAP
5/5 シーン内にどんなUobjectがあるかを調査するコマンド
Obj list
Class Count
MetaData 357
SkeletalMesh 1
Package 575
Class 2393
FontFace 6
BoolProperty 4797
FloatProperty 3788
ObjectProperty 3251
5/5 シーン内にどんなUobjectがあるかを調査するコマンド
Total,DOBP, NumNodes, …,PureTotal,UserFnCount,UserMacroCount,NumSubobjects,NumPropertyObjects
1268,338,105592,18571,2893,2341,19076,15812,2099,305,669,193895,85528
Bluerprint Stats Plugin
タイトルでできること
タイトルでできること
– DisregardGCObject
– 効率的なクラスタの模索 (Can Be In Cluster)
– Uobject 削減の模索
• 例: MacroによるUObjectの肥大
• 例: Nativization によるUobjectの削減
– レベル分割 (GCのための。。。) <-最初に言及
GCのためのレベル分割
• レベルストリームで大きく読み込むと、
一時的にシーン全体のオブジェクトが増え、検索コストが増加する。
• レベルストリームで大きく捨てると、削除コストが増加する。
• GCが小さくなるように、サブレベルを更に細かく分割し、
細かく読み込み、細かく捨てる。
• 最後の最後の手だが、この方針で行くならば、
早めに検証しサイズを見積もらなければいけない。
タイトルでできること
– DisregardGCObject
– 効率的なクラスタの模索 (Can Be In Cluster)
– Uobject 削減の模索
• 例: MacroによるUObjectの肥大
• 例: Nativization によるUobjectの削減
– レベル分割 (GCのための。。。)
DisregardGCObject
Concept
• ゲーム起動時から常にメモリにいて欲しいオブジェクトは必ずある。
絶対にGCしてほしくないので、そもそもそれらをGC対象から外す。
• ゲーム起動時(PreInit())に読み込むものをGC対象外とする。
UObjectArray: UObject(Uproperty)の一次元配列
ゲーム常駐部分
DisregardGCObject
Maximum Object Count Not Considered By GC
Sizeo Of Permanent Object Pool
この2つの数値を設定します。
DisregardGCObjectの設定方法
1. DisregardGCObjectの有効化
[/Script/Engine.GarbageCollectionSettings]
gc.MaxObjectsNotConsideredByGC=1
gc.SizeOfPermanentObjectPool=0
2. 一回起動するとログにこのような数字が出る。
LogUObjectArray: 52083 objects as part of root set at end of initial load.
LogUObjectAllocator: 9937152 out of 0 bytes used by permanent object pool.
3. この数値を、先程の値に再設定
[/Script/Engine.GarbageCollectionSettings]
gc.MaxObjectsNotConsideredByGC= 52083
gc.SizeOfPermanentObjectPool= 9937152
0以外の数にすることで有効になる
上が、PreInitでロードされたUObjectsの総数
下が、PreInitでロードされたUObjectsの総バイト数
DisregardGCObject: 成果
設定前
LogGarbage: 74.040701 ms for GC
LogGarbage: 2.808510 ms for unhashing unreachable objects.
設定後
LogGarbage: 60.583722 ms for GC
LogGarbage: 2.114550 ms for unhashing unreachable objects.
20%ほど改善
タイトルでできること
– DisregardGCObject
– 効率的なクラスタの模索 (Can Be In Cluster)
– Uobject 削減の模索
• 例: MacroによるUObjectの肥大
• 例: Nativization によるUobjectの削減
– レベル分割 (GCのための。。。)
効率的なクラスタの模索
Concept:
複数のUobjectをクラスタ化して、検索コストを減らす。
※本件は削除コストには効果がありません。
上記四項目、
ActorもClusteringに含めるか、BlueprintもClusteringに含めるか。。
効率的なクラスタの模索
Actorの中にCanBeInCluster設定項目がありま
す。
これで、クラスターの中にそのアクターを含め
るかどうかを設定できます。
※DefaultではStaticMeshActorはDefaultで
True。
効率的なクラスタの模索: 注意点
Clusteringされるタイミング
クラスタが作成されるのは、ロードされ、
AddToWorldされるタイミングです。
その後、新たな参照が貼られる様なオブジェク
トは、CanBeInClusterをOffにしなければいけ
ません。
Matinee/SequencerでStaticMeshActorを動
かすときなどは注意。
(Developmentビルドならば、エラーを出して
クラッシュするので調査は簡単です。)
Blueprint Clustering
Blueprintは内部に沢山のUObjectを保持しています。
なので、ブループリント単位でのクラスタリングを行なうと、検索コストをへら
すことが可能です。
と言いつつ、このオプションでクラッシュが発生するとの報告があります。
それらが、回避可能か、エンジンとして修正可能かの情報を集めています。
もしも、クラッシュした場合、UDNにてご報告頂ければ幸いです。
効率的なクラスタの模索: 効果
Actor Clustering
Garbage collection (StaticMeshActor is NOT in GC clusters):
25.840614 ms for GC
Garbage collection (StaticMeshActor is in GC clusters):
14.977702 ms for GC
-------------------------
Blueprint Clustering
Garbage collection (BlueprintGeneratedClass does NOT create GC clusters):
42.674898 ms for GC
Garbage collection (BlueprintGeneratedClass creates GC clusters):
33.523061 ms for GC
タイトルでできること
– DisregardGCObject
– 効率的なクラスタの模索 (Can Be In Cluster)
– Uobject 削減の模索
• 例: MacroによるUObjectの肥大
• 例: Nativization によるUobjectの削減
– レベル分割 (GCのための。。。)
Uobject 削減の模索
UObjectArray: UObject(Uproperty)の一次元配列
UObjectが多ければ必ずヒッチが生じる
Uobject自身を不必要に増やさない対策
Macro -> FunctionによるUPropertyの削減
Concept:
マクロの中にマクロが含まれている -> 最終的に展開されたBPは非常に巨大なものに
例)あるBPから一つのマクロを取り除く -> プロパティー数が1066 -> 600まで減る
マクロは、中にlatentノードがなければ関数化によって取り除くことができます。
上記手法によりあるBPを関数化した場合、
とあるマップで読み込んでいるキャラクター関連のUObjectが
20781から5486へと75%のプロパティ数の削減を実現しました
Macro -> FunctionによるUPropertyの削減: 確認方法
Bluepirnt Stats Plugin
Blueprint stats for 1268 blueprints in XXXXXXXXXXX
Total,DOBP, NumNodes,
…,PureTotal,UserFnCount,UserMacroCount,NumSubobjects,NumPropertyObjects
1268,338,105592,18571,2893,2341,19076,15812,2099,305,669,193895,8552
8
LogBlueprintStats: -------------------------------------------
LogBlueprintStats: MacroName,NumFlatInstances,NumProperties
LogBlueprintStats: ForEachLoop,850,954
LogBlueprintStats: ForEachLoopWithBreak,225,396
LogBlueprintStats: PlayTalkEndAnimation,319,294
LogBlueprintStats: PlayerMoveTo,12,144
LogBlueprintStats: CS Character Setting,0,116
LogBlueprintStats: End Cutscene Matinee,9,99
マクロに特化した情報が出るように
拡張されたコードがあり。。
(4.17で正式採用予定)
欲しい人はご連絡ください。
タイトルでできること
– DisregardGCObject
– 効率的なクラスタの模索 (Can Be In Cluster)
– Uobject 削減の模索
• 例: MacroによるUObjectの肥大
• 例: Nativization によるUobjectの削減
– レベル分割 (GCのための。。。)
Blueprint NativizationによるUobjectの削減
BP Officeでテスト
非Native: Total 31359 Objects
Native: Total 30885 Objects
差: 475 Objects
---主に減っている箇所
StructProperty 4901 4756
FloatProperty 3450 3356
ObjectProperty 2595 2569
IntProperty 1528 1482
BoolProperty 3653 3617
Native化対象のBP
BP Office Sample
タイトルでできること
– DisregardGCObject
– 効率的なクラスタの模索 (Can Be In Cluster)
– Uobject 削減の模索
• 例: MacroによるUObjectの肥大
• 例: Nativization によるUobjectの削減
– レベル分割 (GCのための。。。)
アジェンダ
1. Load時間(及びパッケージサイズ)のProfilingとOptimization
2. GCのProfilingとOptimization
まとめ
• コンソール開発は、
常にパフォーマンスを意識して開発する必要があります
• なるべく早期から、実機で、定期的な
パフォーマンス検証を行ってください
• UE4は調整、検証を行える様に様々なツールを用意しております
• 疑問点やヘルプは、
UDNもしくは弊社サポートまでお問い合わせください

[4.20版] UE4におけるLoadingとGCのProfilingと最適化手法