GCのReachabilityAnalysisの負荷を軽減したい

お世話になっております。

<br/>

GCの負荷軽減で相談です。

<br/>

現在GCでReachabilityAnalysisが入る際に処理時間が長く対策したいと考えてます。

<br/>

UObjectが40万近くあるのでこちらの削減もしているのですが、なんかしらGCの時間を削ったり、もう少しFrameに分割して対応することは出来ないでしょうか?

<br/>

なにかエンジン側の設定などありましたら教えていただきたいです。

<br/>

よろしくお願いいたします。

<br/>

[Image Removed]

<br/>

[Image Removed]

<br/>

<br/>

追加ですみません、インクリメンタルGCでの初回のReachability AnalysisとGatherを同一フレームではなく複数のフレームに分けてもう少し1フレームでの処理を分割することは出来ないでしょうか?

お世話になっております。

PerformReachabilityAnalysisOnObjectsInternal の負荷を減らすことが目的かと思いますが、まず一般的な考え方として Object数を削減をすることは効果的な方法の1つです。

非同期化はされていませんが、時分割処理は可能です。以下のドキュメントをご参考ください。

https://dev.epicgames.com/documentation/ja\-jp/unreal\-engine/incremental\-garbage\-collection\-in\-unreal\-engine?application\_version\=5\.4

実験的機能ですが、以下の設定は有効にするための設定例です。

[ConsoleVariables]
gc.AllowIncrementalReachability=1 ; enables Incremental Reachability Analysis
gc.AllowIncrementalGather=1 ; enables Incremental Gather Unreachable Objects
gc.IncrementalReachabilityTimeLimit=0.002 ; sets the soft time limit to 2ms

その他、以下は一般的な対処方法や設計時に考慮するべき事項です。

  • TStrongObjectPtrの使用を避ける
  • gc.AssetClustreringEnabled 等、オブジェクトのクラスタ設定を有効する
  • クラスから他のオブジェクトへの参照の数を制限する
  • WP の場合: 可能な場合はグリッドとセルの数を減らす
  • Disregard GC Object を利用して常駐が必要なオブジェクトを到達可能分析から除外する

通常 IncrementalPurgeGarbage における1フレーム処理内の時間制限は GIncrementalGCTimePerFrame ( gc.IncrementalGCTimePerFrame = 0.002f; // 2ms ) で制限されていますが、インクリメンタル処理の最後の処理はフルパージが行われるため、このタイムアウトとは関係なく参照する全てのオブジェクトの破棄が行われて 2ms以上かかることがあります。この IncrementalPurgeGarbage が発生しているのが gc.TimeBetweenPurgingPendingKillObjects によるものだとしたら、この定周期の期間を短くしてより頻繁にGCすることでヒッチとなりうるオブジェクト数を抑えるか、それ以外には上記で挙げたようなオブジェクトを減らすような工夫が必要になります。メモリの破棄プロセスになるので大量のオブジェクトが残っていることは重くなることに直結します。

ご確認頂きありがとうございます。

> 最初のGC時が1~2msになるのを対処するにもオブジェクト数周りを減らす以外に対処方法は無いでしょうか?

基本的にオブジェクト数を減らすことが効果が大きいですが、例えばオブジェクトを削除するのではなくプールして再利用する方法、このケースでは Post Tick Component Update の負荷が高いため、ここで処理をしているオブジェクトを特定してPost Tick処理をスキップできないか検討する方法や、Post Tick Component Update 、GC.VerifyAssumptions、GC.VerifyObjectFlags は複数スレッドで処理するので他スレッドが使用されているかを確認することなどがあります。とはいえこのスクリーンショットでは Game Threadは5.4msを後半待機しているので、Game 以外のスレッドでのパフォーマンスも確認いただくことをお勧めします。

> 最後のフルパージはエンジン仕様で必ず起こるものという認識でよろしいでしょうか?

はい、この動作はエンジンの仕様です。

ご返答ありがとうございます。

ご連絡遅れて申し訳ありません。

gc.TimeBetweenPurgingPendingKillObjectsはデフォルトの60のままだったのでこちらを短くしてみます。

また、GC.VerifyAssumptions、GC.VerifyObjectFlagsをハイライトイベントで探ってみてもトレースした情報では見つけることができませんでした。

こちら、他スレッドでも使用されてないようですがなにか問題があるのでしょうか?

度々すみませんがこのあたりの調査などの方法を教えていただけると幸いです。

ありがとうございます!

Win64のTestビルドパッケージでちぇっくしてみたのですが​、GC.VerifyAssumptions、GC.VerifyObjectFlagsは検索などにもヒットしませんでした。

ConditionalCollectGarbageの中も全て見てみたのですが、表示はありませんでした。

[Image Removed][Image Removed]

Testビルドにおいては 最適化の観点からNamedEventの出力がデフォルトで制限されているため、当該イベントが表示できていない可能性が高いです。

GlobalDefinitions.Add("ENABLE_STATNAMEDEVENTS=1");参考:Testビルドでも色々計測できるようにする - だらけ者だらけ

こちらをTarget.cs に追加してTestビルドで計測するか、Developmentビルドではマーカーが表示されていることかと思われます。

ありがとうございます。

再度ビルドしなおして確認してみます!

念のためこちらでもUE5.5.4のバージョンで確認してみたのですが、GC.VerifyAssumptions、GC.VerifyObjectFlags のマーカーはTestビルドでも表示することができていました。実施したのは PROJECT.Target.cs の中に GlobalDefinitions.Add(“ENABLE_STATNAMEDEVENTS=1”); の定義を追加してTestビルドパッケージを作成し、-statnamedevents の引数を追加してプロファイルを取得しただけとなります。画面上に "NamedEvent"が有効になっている青色の文字が表示されていればNamedEventは有効になっているはずです。

再度ビルド確認してみたのですが、表示されませんでした。5.4なのが問題でしょうか?

[Image Removed]​

[Image Removed][Image Removed]

お世話になっております。

こちらで色々調査や、パラメータを調整して少し進展がありました。

パラメータをクラッシュしないギリギリの値に設定したら、GCの開始以外はこちらが求める500us以下ぐらいになるようになりました。

gc.IncrementalReachabilityTimeLimit=0.00005

gc.IncrementalGatherTimeLimit = 0.00005

gc.IncrementalGCTimePerFrame = 0.00005

gc.TimeBetweenPurgingPendingKillObjects=0.0001

ただし、GC開始時だけはまだ1msを超える状況でこちらを対処する方法を知りたいです。

[Image Removed]

[Image Removed]

[Image Removed]

フルパージに関しては、開始からゲーム内でレベルを遷移するか、強制GCを叩いたときのみになったのですが、この状態でも問題ないでしょうか?

[Image Removed]

こちらとしてはなんとしてもGCを500us以下にしたいので、GCの頻度などを調整したりエンジンに手をいれる必要があれば対応しようと思ってます。

また、トレースをお渡しして細かく調査していただたほうが良い場合はプライベートのスレッドを新規作成して相談したいと思います。

以上、よろしくお願い致します。

GCの処理時間を500μs以下にすることは、私たちが行ってきたサポートを通じてもそれらを実現したという話は聞いたことがなく、また社内でもそこまでの厳しい制限に対する検証が行われてはこなかったので、500μs以下という値が現実的かどうかは少し検討の余地があるように思います。少なくとも、これらの値は現在GCの対象となるオブジェクトだけでなくアセットのリファレンス構成にも依存する部分であるため、一概にプロファイルだけを見ても判断がつきかねる部分ではございます。

この値の目標値を実現することは不可能ではないと思われますが、​少なくともこれまでに述べてきた最適化の内容を実施済みであり、プロジェクトにおいて十分に最適化の検証を行うことは必須となる目標値です。そのうえで、更に最適化が必要な箇所においてはプロファイルを行いマーカーが長い箇所に対してステップ実行やログ出力(LogGarbage)もしくは関数単位でのプロファイリング・最適化、GC分析のコンソールコマンド(gc.DumpAnalyticsToLog 1)を行いながらご検証頂くこともできます。

ご確認頂きありがとうございます。

そうですね、現状調整を頂いているかと思いますが、GCの処理負荷(単発でのヒッチ)が気になる場合はインゲーム中の影響がない部分でGCを実行するようにタイミングを調整して頂くこともご検討頂けますと幸いです。

お世話になっております。

GCの方ですが、Win環境下ではなんとかなる形にはなりました。

ただPS5ではLevelStreaming周りでフリーズが起きてしまうのが発覚し

条件はgc.AllowIncrementalGather=1 でした。

まだ深堀りしてませんが、下記のスレッドの内容が近いのでPS5では一旦使わないようにする流れで負荷下げることになりそうです。

[Content removed]

お世話になっております。​

フリーズするという事に対して更に掘り下げる場合はトレースをしてどの箇所でフリーズしているか深掘りをする必要がありますが、gc.AllowIncrementalGather についてはまだ5.6においても実験的な機能となっているようです。これがパフォーマンスに影響する場合は、フラグの切り替えによって負荷が変わるようなケースでは一時的にオフにすることも可能です。

あとAllowIncrementalReachabilityでも起きることが確認しててこちらのほうが頻度たかいのでこちらも使わないようにする流れにしました。

フリーズ際に起きやすい箇所はLevelStreamingの処理が大量に起きていて

LogLevelStreaming: Warning: Couldn’t find ULevel object in package というログが大量で出ている箇所でした。

フリーズする箇所もこのログが出る

AsyncLevelLoadCompleteのあとに

RequestLevel周りをループしてフリーズしてる流れでした。

後々トレースをして深堀りしてみます。

一旦この実験的機能は使わずに調整してみようと思います。

お世話になっております。

AllowIncrementalReachabilityとAllowIncrementalGatherでのフリーズは、今回対象のプラットフォームすべてでフリーズが起きるので使用することを断念しました。

時間的な理由で、フリーズの調査を現状できないので一旦保留にしました。

そこでまた、相談があります。

下記のようにパラメーターを調整しました。

gc.IncrementalReachabilityTimeLimit = 0.0001

gc.IncrementalGatherTimeLimit = 0.0001

gc.IncrementalGCTimePerFrame = 0.0001

gc.TimeBetweenPurgingPendingKillObjects = 0.01

PS5とXSXでは下記画像のように、GC開始からほぼマイフレイーム細くGCをすることでこちらの意図通りの負荷になっています。

PS5

[Image Removed]

XSX

[Image Removed]

ですが、Win環境では上記のような動きにはならずでした。

[Image Removed]

PS5、またはXSXのような状態に近づけたいのですが

それができていない原因が探れずです。

どのような原因が間変えられるでしょうか?

以上、よろしくお願いいたします。

ご説明頂きありがとうございます。

GarbageCollectionの実行期間を示すマーカーは以下のリージョンマーカーのタイミングによって違っているはずです。おそらくコンソールはこれらのBegin/Endが頻繁に呼び出されていますが、WindowsではBegin/Endの実行までにかなりの差があるはずです。

TRACE_BEGIN_REGION(TEXT(“GarbageCollection”));

TRACE_END_REGION(TEXT(“GarbageCollection”));

まずはコンソールとWindowsで TRACE_END_REGION(TEXT(“GarbageCollection”)); が実行される条件をご確認頂くのが早いかと思いますが、これが呼ばれるタイミングがGCのFullPargeの実行タイミングなので、そこに違いがあると思われます。

今のところまだ、原因は解決していません。

ワーカスレッドのタイミングなのかどうかで調査中ですが、

Win環境ではなかなかインクリメンタルGCに入る前のワーカースレッドで作業中かどうかのところでGCに入らないのは確認できています。

このあたりは各ハード側の問題やPCの構成で変わったりするものなのでしょうか?