Memreportでメモリリークの原因を調査

[Unreal Engine (UE) Advent Calendar 2022](https://qiita.com/advent-calendar/2022/ue) の16日目の記事となります。

# はじめに

コンソールコマンドのMemreportを使用してメモリリークの原因を調査したので、その時の対応を書き出してみました。

確認環境はUE5.0.3になります。
間違いなどありましたら、ご指摘いただけると有難いです。

# 経緯

イベントに展示するゲームのビルドを作成する際に、デモループを追加しました。
デモループは何も操作されない状態で長時間動き続ける可能性があるため、それを想定した長時間の放置確認も合わせて行いました。

8時間ほどデモループを流し続けた時点で、クラッシュすることもなく動き続けていて安心したのですが、その状態でプレイすると、処理が落ちが発生していました。

最初は何が起きたか分からず、タスクマネージャーを確認したところ、メモリの使用量が起動時の状態から300MBほど増加していました。起動直後は約300MBのメモリ使用量だったので倍に増えています。リソースを保持している可能性もありましたが、起動以降はリソースの読み込みもほぼ無いはずなので、メモリリークが発生していることが疑われます。

この時点で処理落ちの原因はわかりませんでしたが、メモリリークの状態を放置するのはまずいので、まずはそこを起点に調査していくことにしました。

# 調査したこと
メモリの調査方法について調べたところ、丁度良い記事を見つけました。
以下の記事を参考に、Memreportでメモリの状況を確認していくことにしました。

https://historia.co.jp/archives/25131/

確認方法は簡単で、コンソールコマンドでmemreportと入力すると、
![20221203_Memreportコマンド.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165865/cbcf31aa-2b5f-888d-5787-7e3e930ef896.png)
Saved以下のフォルダにファイルが出力されます。拡張子は「memreport」ですが、内容はテキスト形式です。![20221203_Memreport出力先とファイル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165865/cfb9b74d-830c-68c8-752d-20ecc86b40be.png)
そのまま内容を見ても該当の箇所が分かる場合もありますが、出力タイミングの異なるファイルに対してdiffツールを使用することで、変化しているメモリに関わる部分を視認しやすくなります。

# 実際に何が起こっていたのか
まずは、起動直後とデモループを何度か繰り返して再生した後のMemreportを出力して差分を確認してみました。diffツールにはWinMergeを使用しています。

差分に一通り目を通すのも良いのですが、量が結構あるので、ある程度見る場所を絞った方が良いと思います。
長時間動いていてクラッシュしていないなら、リークが大量に発生しているとは考えにくいので、変化が少ない箇所から見ていくのが良さそうです。

変化の少ない下の方から見ていくと、どうやらテクスチャの項目の数値が増加している部分がありました。ファイル名を見ると、Niagaraで参照しているテクスチャでした。
![20221124_実際のMemreportのdiff_参照テクスチャ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165865/f7b9b7ae-c1d3-f123-df9d-847f9f5af3cb.png)
ちなみに、末尾から3番目の数値は「Usage Count」(使用数)です。使用数が増えているということは、メモリの使用量も増えていると考えられます。![20221124_実際のMemreportのdiff_項目.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165865/e964a7ca-3a0b-22cc-7f7e-67eea37d0305.png)
今度はNiagaraに当たりをつけて調べると、NiagaraComponentの数値が 287-199=88 増加しています。
これは、先ほどのテクスチャの使用数 240-152=88 と同等の増加です。これは偶然ではなく、Niagaraのエフェクトが残り続けていると考えて良さそうです。
![20221124_実際のMemreportのdiff_Niagaraコンポーネント.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165865/bcd1deb5-3ed5-c146-99bd-db65754d5890.png)
コンポーネントが残っているということは、何らかの管理処理が動いていると想定できるので、処理落ちの原因になっている可能性が高いです。
他に目立ったエフェクトが残っていないようなので、このエフェクト自身の発生方法や設定に何かありそうだと予想されます。

# 原因は？
該当のエフェクトは、発生時にAuto DestroyをONにしているので、再生が終了した時点で特に問題無く消えてくれそうでした。
そうなると設定自体に問題がありそうなので、とにかく他のNiagaraエフェクトと比較したところ、存続期間に関するパラメータで以下の違いがありました。

上が間違っているときの状態で、下が正しい状態です。
![20221024_メモリリークの原因と思われる_クローズアップ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165865/5cdd29f1-28c3-c8bc-5977-719b9bf7509e.png)
`Loop Behavior`のヘルプを見ると以下の説明があります。![20221113_LoopBehaviorがOnceの時の説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165865/c977acd0-8bcd-b60b-c393-58103fcc84d2.png)
エミッターの存続期間が`Loop Duration`を超えると非アクティブ状態に入るとあります。これは、`Loop Duration Mode`が`Infinite`だと、非アクティブ状態にならないということになるのではないか？という疑問が出てきました。

# 確認
その設定で本当にエフェクトが残り続けるのかという疑問を解消するため、状況を再現して確認してみました。
`Loop Behavior`が`Once`の同じエフェクトを2つ用意して、それぞれの`Loop Duration Mode`を`Fixed`と`Infinite`に設定して発生させてみました。

それぞれ同時に50個ずつ発生させた後のNiagaraデバッガの表示です。NS_Onceが`Fixed`の設定の方で、再生後にActiveが「0」になっています。`Infinite`を設定したNS_OnceInfinityの方が、発生した数だけActiveが残っています。メモリも使用されたままです。
![20221123_アドベントカレンダー_Niagaraデバッグ情報_50個残る.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165865/d5c90285-1939-4d8e-f538-f932d8a78f17.png)
ざっくりとした確認ではありますが、パラメータ設定ミスが原因で、エフェクトがActiveのまま残り続けることが確認できました。

# まとめ
今回のメモリリークの原因は、エフェクトの単純な設定ミスでした。色々パラメータを変更している際に、戻し忘れてしまったものと思われます。
設定を正しく修正することで、メモリリークの現象は収まり、8時間のデモループ後のプレイでも処理落ちしなくなりました。

ちなみに、コンポーネントに紐づけていないNiagaraエフェクトは、パーシスタントレベルを終了させれば全部消えるようです。

ゲームにもよると思いますが、パーシスタントレベルを保持したまま、シーンを遷移する実装を行った場合などは、何らかの解放忘れが発生する可能性が高くなるかと思います。

見た目の派手さも無く地道な作業ですが、機会を見てMemreportでメモリの状況を確認すると楽しい発見があるかもしれません。