65.2K Views
August 02, 18
スライド概要
以前出した「 UE4におけるLoadingとGCのProfilingと最適化手法」の4.20版です。
元スライドのURL: https://www.slideshare.net/EpicGamesJapan/ue4loadinggcprofiling/
4.20からGCのSweep処理がが複数フレームで分散可能になったので、そちらを追記いたしました。
(UE4でローディング時間やGCによるカクつき(ヒッチ)を軽減したい場合に、どの様に問題部分を特定し、またどの様に最適化するかのはじめの一歩をまとめました。ご参考になれば幸いです。)
Unreal Engineを開発・提供しているエピック ゲームズ ジャパンによる公式アカウントです。 勉強会や配信などで行った講演資料を公開しています。 公式サイトはこちら https://www.unrealengine.com/ja/
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 Memory (NAND Flush 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 • • • • • ロード時間計測用のStat 灰=パーシスタントレベル 赤=未ロード 紫=ロード中 橙=AddToWorld 緑=ロード完了
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のみ。)
25 17 全部つけたもの vs 全部消したもの シェーダ数を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 Doc: アセット管理 【UE4】AssetManagerを使用したレベルストリームの高速化 メモリが許すかつロードできるアセットが事前にわかる場合、 前読みも検討できるかと思います。
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のフレーム分散 4.20以前 GCのコスト = 検索コスト + 削除コスト と記載しましたが、正確ではありません 削除コスト = 依存関係の切断 + 実際のオブジェクトの削除 こちらはフレーム分散可能 GCが呼び出されたフレームのインパクト = 検索コスト + 依存関係の切断 今日はこれを便宜上「削除コスト」と呼びます
削除コストの詳細 GCのフレーム分散 4.20で更新 GCのコスト = 検索コスト + 削除コスト と記載しましたが、正確ではありません 削除コスト = 依存関係の切断 + 実際のオブジェクトの削除 どちらも分散可能に!! GCが呼び出されたフレームのインパクト = 検索コスト + 依存関係の切断の1フレーム目 今日はこれを便宜上「削除コスト」と呼びます
4.20以前 つまり、 GCのコスト = 検索コスト + 削除コスト
4.20で更新 つまり、 GCのコスト = 検索コスト + 削除コスト 複数フレームに分散されるのでカクツキの 原因にはなりにくくなるはずです。
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. 2. 3. 4. 5. Log loggarbage log (Log loggarbage verbose) Stat dumphitches (Stat Startfile/Stopfile) CBD Profiling Tools 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 • Development Buildで基本ON (Test/Shippingでは基本無効) • CollectGarbageInternal.VerifyGCAssumptionsが走ってGCが激重に • なしにするには、実行時に”-NOVERIFYGC”オプションをつけて起動 GCの本来のコストを見積もるならば、 TESTビルドで検証することを強くおすすめします。
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があるかを調査するコマンド Bluerprint Stats Plugin Total,DOBP, NumNodes, …,PureTotal,UserFnCount,UserMacroCount,NumSubobjects,NumPropertyObjects 1268,338,105592,18571,2893,2341,19076,15812,2099,305,669,193895,85528
タイトルでできること
タイトルでできること – 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 UObjectArray: UObject(Uproperty)の一次元配列 ゲーム常駐部分 Concept • ゲーム起動時から常にメモリにいて欲しいオブジェクトは必ずある。 絶対にGCしてほしくないので、そもそもそれらをGC対象から外す。 • ゲーム起動時(PreInit())に読み込むものをGC対象外とする。
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 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. 上が、PreInitでロードされたUObjectsの総数 下が、PreInitでロードされたUObjectsの総バイト数 3. この数値を、先程の値に再設定 [/Script/Engine.GarbageCollectionSettings] gc.MaxObjectsNotConsideredByGC= 52083 gc.SizeOfPermanentObjectPool= 9937152
DisregardGCObject: 成果 設定前 LogGarbage: 74.040701 ms for GC LogGarbage: 2.808510 ms for unhashing unreachable objects. 20%ほど改善 設定後 LogGarbage: 60.583722 ms for GC LogGarbage: 2.114550 ms for unhashing unreachable objects.
タイトルでできること – 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: LogBlueprintStats: LogBlueprintStats: LogBlueprintStats: LogBlueprintStats: LogBlueprintStats: LogBlueprintStats: ------------------------------------------MacroName,NumFlatInstances,NumProperties ForEachLoop,850,954 ForEachLoopWithBreak,225,396 PlayTalkEndAnimation,319,294 PlayerMoveTo,12,144 CS Character Setting,0,116 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 FloatProperty 3450 ObjectProperty 2595 IntProperty 1528 BoolProperty 3653 4756 3356 2569 1482 3617 BP Office Sample Native化対象のBP
タイトルでできること – DisregardGCObject – 効率的なクラスタの模索 (Can Be In Cluster) – Uobject 削減の模索 • 例: MacroによるUObjectの肥大 • 例: Nativization によるUobjectの削減 – レベル分割 (GCのための。。。)
アジェンダ 1. Load時間(及びパッケージサイズ)のProfilingとOptimization 2. GCのProfilingとOptimization
まとめ • コンソール開発は、 常にパフォーマンスを意識して開発する必要があります • なるべく早期から、実機で、定期的な パフォーマンス検証を行ってください • UE4は調整、検証を行える様に様々なツールを用意しております • 疑問点やヘルプは、 UDNもしくは弊社サポートまでお問い合わせください