[UE4] MemProを使用したメモリトラッキング

# 1. はじめに
[検証バージョン：4.23.0]
　MemProはC++メモリトラッキングツールで、UE4は4.23からインテグレーションに対応しています。
本記事では **[MemPro](https://www.puredevsoftware.com/index.htm)** を使用してUE4アプリケーションのメモリトラッキングを行う方法について説明します。
![Summry.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364110/006d2386-2dbf-47c7-4b7d-d0431a7d7fbf.png)

# 2. MemProの利用にあたって
　MemProをUE4で利用する上で、導入を検討する内容としてご参考ください。

## 2.1. 機能
・LLMタグベースでのメモリをトラッキングします
　(UE4でメモリトラッキング時にはLLMタグの指定が必須)
・関数別、タイプ別での追跡機能や差分の検出機能があります
・キャプチャしたデータをエクスポートできます

## 2.2. メリット
・イテレーションやセットアップのしやすさの点でMalloc Profilerよりも優れています[^1]
・コールツリーの機能を持つためLLMよりもトラッキング精度の面で優れています[^2]
・トラッキング処理のコストも比較的低く、比較的長時間のトラッキングが可能です[^3]

[^1]: Malloc Profilerは「フルビルドが必要、かつEditor起動時もトラッキングが走る点」が少しやっかい
[^2]: LLMは「どの経路で確保されたメモリであるかが分からない点」が少し難点
[^3]: ツールによるメモリトラッキングは「トラッキングコスト、キャプチャに必要なメモリ」などの観点から悩ましい点

## 2.3. 注意点
・MemProは有償のツールで、トライアルで30日間無償で利用可能、[[MemPro]->[Purchase]](https://www.puredevsoftware.com/mempro/Purchase_paddle.htm)から購入可能です
　(1マイナーバージョンアップグレードの永久ライセンス：JP¥15,390/1ユーザーライセンス)[^4]

[^4]: 2019/12/18 時点

# 3. MemProで出来ること
　以下にMemProツールで出来ることを示します。予め出来ることを把握することで、メモリトラッキングの目的に合致しているかを確認しておくとより効果的です。

## 3.1. Memory Graph
　使用メモリ量をグラフ化して推移を確認できます。
![2019-11-20_02h40_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364110/3692f180-37bd-2813-ff80-f226bae5b6b0.png)

## 3.2. Call Tree
　メモリがAllocateされるまで経路の階層化表示します。
![1.CallTree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364110/512b9433-344f-f7d3-62e7-6939092ab0aa.png)

## 3.3. Page View
　メモリ断片化の内容を表示することができます。
![2.PageView.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364110/3edce9b4-2d70-464e-6e1d-8934db4a694a.png)

## 3.4. Functions
　メモリの内訳を関数別に表示することができます。
![3.Functions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364110/92df953b-8065-b133-32f2-320b82fcd0f7.png)

## 3.5. Types
　メモリの内訳をタイプ別に表示することができます。
![4.Types.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364110/57fcfa78-bd1d-4089-b885-121adaf5eaf1.png)

その他、ツールの使い方は[MemPro Users Guide](https://www.puredevsoftware.com/MemProUserGuide/Introduction.htm)で説明されています。

# 4. 使い方

### 1) 準備
MemPro用のトラッキングを有効にするため **[Project].Target.cs** に以下のように **MEMPRO_ENABLED=1** を追加します。以下はWin64のみトラッキングを適用する例です。

```cs:TP_423.Target.cs
public class TP_423Target : TargetRules
{
	public TP_423Target(TargetInfo Target) : base(Target)
	{
		Type = TargetType.Game;
		ExtraModuleNames.AddRange( new string[] { "TP_423" } );

		// Win64でのみMemProトラッキングを有効にする場合以下を追加
		if (Target.Platform == UnrealTargetPlatform.Win64)
		{
			GlobalDefinitions.Add("MEMPRO_ENABLED=1");
		}
	}
}
```

もしエディタで計測を行いたい場合は、プロジェクトではなくエディタのターゲット  **[Project]Editor.Target.cs** に追記してください。以下はその例です。

```cs:TP530Editor.Target.cs
public class TP530EditorTarget : TargetRules
{
	public TP530EditorTarget(TargetInfo Target) : base(Target)
	{
		Type = TargetType.Editor;
		DefaultBuildSettings = BuildSettingsVersion.V4;

// ここから
  		BuildEnvironment = TargetBuildEnvironment.Unique;
  		GlobalDefinitions.Add("MEMPRO_ENABLED=1");
// ここまで
		ExtraModuleNames.AddRange( new string[] { "TP530" } );
	}
}
```

### 2) 起動とキャプチャ
　メモリのトラッキングにはLLMを利用するため、起動引数として ***-llm*** を指定する必要があります。
それから ***"MemPro.LLMTag [tag]"*** で追跡するタグを選択し、***"MemPro.Enabled 1"*** でキャプチャを開始できます。キャプチャを終了する場合はアプリケーションを終了すると自動で停止します。MemProのLaunchから起動引数を定義して起動することも可能です。

```cs:起動引数フォーマット
// [LLM_TAG]を好みの設定に併せてください
-llm -nothreadtimeout -execcmds="MemPro.LLMTag [LLM_TAG]" 
```

以下のケースでは、アプリケーション起動時から全LLMタグのトラッキングを開始します。

```cs:例1：全LLMタグでのメモリトラッキングをアプリケーション起動時から開始
-llm -nothreadtimeout -execcmds="MemPro.LLMTag *, MemPro.Enabled 1" 
```
以下のケースでは、StaticMeshのメモリをトラッキングします。このケースでは起動時に有効にするコマンドを含めていないので、 ***"MemPro.Enabled 1"*** を任意のタイミングで実行してキャプチャを開始することができます。

```cs:例：StaticMeshタグでのメモリトラッキング準備
-llm -nothreadtimeout -execcmds="MemPro.LLMTag StaticMesh" 
```
ここで指定することが可能なタグは **LLM_ENUM_GENERIC_TAGS** のリストを参照。以下の例では "Untagged" に該当します。

```
	macro(Untagged,	"Untagged",	NAME_None,	NAME_None,	-1)\
```

キャプチャしたプロファイルは **[Project]/Saved/Profiling/MemPro/** に **.mempro_dump** 形式で保存されます。
![capture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364110/db02124c-1a10-2884-ebf5-2ea262beee83.png)

# 5. プロファイルの解析
　MemProでキャプチャした結果をMemProで解析する基本的な方法について記載します。

## 5.1. MemProでのロードと解析
　MemProでキャプチャを解析する際にシンボル情報を指定することで関数レベルでの情報を確認することができます。以下にMemProでシンボルファイルのパスを指定する方法を記載していますが、予めパッケージにシンボルファイルを含めておくことでキャプチャにシンボル情報を含めることもできます。

**・シンボルファイルのパス指定**
![7.Symbol.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364110/77ad89a8-d21c-8135-809d-be2f5953f63c.png)
**・デバッグファイルをパッケージに含める**
予めProject SettingsでInclude Debug Filesを有効に設定しておく必要があります。
![2019-09-14_01h26_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364110/959969ae-43c3-b464-e85c-979e9afc2e88.png)

## 5.2. リークの解析
### 1) DiffSnapshotの取得
メモリの増減が発生している箇所の差分情報を取得するため、Memory Graphから差分の範囲を **Shift+マウス右クリック(ドラッグ)** で選択します。選択後に **Show Difference** を選択すると差分情報が **「Snapshots」** に保存されます。

:::note warn
2023/10/27時点では、左ドラック選択でも行けるようです。
:::

![2019-11-26_12h30_35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364110/4b7a6f54-f686-c727-3788-0a6e1bd0d826.png)

### 2) CallGraphから差分のチェック
上記で保存された差分スナップショットを右クリックで選択して **「Call Tree」** を選択します。ロードが完了したら差分に関するCall Treeが表示されるため、どの経路で確保されたメモリが残存しているかを確認してリーク箇所を特定します。下の図は「キーを入力した時にCharacterをSpawnした際のメモリ増加箇所」をキャプチャしたものです。APlayerController::ProcessPlayerInputでの入力をトリガに、UWorld::SpawnActorが実行されて、最終的にSkeletalMeshComponentのPhysicsからメモリが確保されていることが分かります。
![2019-11-20_15h05_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364110/702919c6-c176-1546-d878-c120846b66aa.png)
(※クリックで拡大)

## 5.3. プロファイル時の注意点
・全LLMタグを指定することで全てのメモリをトラッキングできますがトラッキング量も多くなります
・モバイルなどでキャプチャする場合はLLMタグを指定することをお勧めします
　（予めどのLLMタグでリークが発生しているかを特定しておく必要があります）

# 6. まとめ
　UE4アプリケーションに関するメモリトラッキングの手段は他にも様々な方法(stat memoryコマンド、Memreportコマンド, Malloc Profiler, LLM, 専用のトラッキングツール等)がありますが、MemProは他のツールとしても非常に使いやすいので[^5]是非お試しください。

[^5]: ※個人の感想です

**[関連記事]**
[公式ブログ：デバッグとメモリーの最適化](https://www.unrealengine.com/ja/blog/debugging-and-optimizing-memory)
[公式ブログ：アンリアル エンジン 4 のメモリリーク対策](https://www.unrealengine.com/ja/tech-blog/dealing-with-memory-leaks-in-ue4)
[Qiita：[UE4] LLM (Low Level Memory Tracker)を使用したメモリトラッキング](https://qiita.com/donbutsu17/items/dd410cd6ee53b0b348ca)
[Qiita：[UE4] Malloc Profiler/Memory Profiler2を使用したメモリトラッキング](https://qiita.com/donbutsu17/items/a72a282587390f43d12d)