[UE4] Malloc Profiler/Memory Profiler2を使用したメモリトラッキング

#1. はじめに
　**Malloc Profiler**はEngineに組み込まれているツールでメモリのプロファイルを書き出します。ここで書き出したプロファイルは**Memory Profiler2**というツールで読み込むことでメモリリークやメモリ関連の問題を発見するために役立ちます。Malloc Profilerは、WindowsでのサポートをメインにLinux/iOS/Macでも機能します(Androidは...)。しかしプロファイルにあたっては膨大な十分なメモリを必要とするため、計測にはパッケージ済のWindowsビルドでプロファイルを実行するのが概して好ましいです。
　検証は4.21.1にて実施しています。
![2018-12-16_14h21_38.png](https://qiita-image-store.s3.amazonaws.com/0/101019/3285d1eb-3ac6-a081-0cab-5b2ea0cb5e01.png)

Malloc Profilerの実行ファイルは`\Engine\Programs\MemoryProfiler2\Binaries\MemoryProfiler2`にあります。

#2. 注意点
　事前に認識しておくべきいくつかの注意点があります。
**・Profileを有効にする際、プロジェクトをリビルドする必要があるため時間を要します**
**・Profileを実行すると膨大なプロファイリングのメモリを必要とするためPCで実行するのが安全です**
**・上記の理由により、メモリが枯渇するため長期間キャプチャしないようにしてください (無操作15分で2GB程度になります)**

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

##3.1. Callgraph View
　メモリがAllocateされるまで経路の階層化表示します。Topdown式でMemory Allocationの概要が分かります。もしパッケージしたゲームで表示されない場合は、<font color="Red">予めProject Settingsで**Include Debug Filesを有効**に設定しておく必要があります。</font>
![1.CallGgraph.png](https://qiita-image-store.s3.amazonaws.com/0/101019/d1b13352-4275-856e-000e-9e7b53bee604.png)

##3.2. Exclusive View
　メモリリークを発見するには Exclusive view が最も役に立つはずです。デフォルトではプロジェクト起動以降に実行された全てのMemory Allocationが表示されます。サイズの大きいAllocationを見つけるのに有効です。
![2.Exclusiveview.png](https://qiita-image-store.s3.amazonaws.com/0/101019/099cfe03-f924-bc5a-2588-2dc957cb6574.png)

##3.3. Timeline View
　Timeline Viewは時間経過に伴ったMemory Allocationの概要が表示されるので、リークしていることを傾向的に確認することができます。またCustom Mark Points(独自のMarker)も作成することができます。そのためには、タイムライン上の特定のポイントを選択します。再度、プロファイルを開くとこれらのMark PointsがStart/Endのドロップダウンメニューで利用できるようになります。
![3.TimelineView.png](https://qiita-image-store.s3.amazonaws.com/0/101019/e612a79c-e63d-34ab-55f4-a408b02544f7.png)

##3.4. Histgram
 棒軸グラフで統計データを表示します。メモリの占有率を視認しやすいため、何にメモリを多く使用しているかを確認することができます。
![4.Histgram.png](https://qiita-image-store.s3.amazonaws.com/0/101019/21ed898a-fc6e-2882-a358-784411716df0.png)

##3.5. Memory Map
 Memory Mapを表示します。メモリを効率的に使用できているかを確認することができます。
![5.MemoryMap.png](https://qiita-image-store.s3.amazonaws.com/0/101019/195312c6-04dc-021f-cf4e-a2c8b2d8e046.png)

##3.6. Details View
 Point-To-Pointでメモリの増減を確認することができます。Start/Stopはプロファイルされた2点を上部のDiff Start/Diff Endから調整することができます。Diffはその差分情報を示し、現象した場合はマイナス値で表示します。
![6.DetailsView.png](https://qiita-image-store.s3.amazonaws.com/0/101019/058b15fc-8ec8-12e9-9de1-e8eca5f7ef22.png)


#4. 準備
 プロファイルを開始するまでに事前に準備が必要です。以下に手順を示しますが、**準備を開始する前に全ての手順を確認してから実施することをお勧めします(これは準備に時間がかかることと、Editorでの動作に影響が出るため)。**

**[準備手順]**
① Memory Profiler2の起動確認
② Malloc Profileの有効化
③ デバッグファイルをパッケージに含める
④ パッケージの作成

##4.1. Memory Profiler2の起動確認
 Memory Profiler2が動作可能なことを先に確認しておきます。もし、Engine/Programs/MemoryProfiler2/Binariesに実行ファイル(.exeファイル)が存在していない場合は、先にMemory Profiler2の実行ファイルを作成するためにコンパイルする必要があります。コンパイルするためには、Engine/Source/Programs/MemoryProfiler2/MemoryProfiler2.slnを起動してプロジェクトをビルドします。ビルドが完了したら、実行ファイルが作成されるためMemory Profiler2が起動できることを確認します。
![2018-12-15_23h38_00.png](https://qiita-image-store.s3.amazonaws.com/0/101019/cf3ea7e9-3b28-09fe-4431-08c470b531d9.png)![2018-12-15_23h41_52.png](https://qiita-image-store.s3.amazonaws.com/0/101019/4ee53a2e-3d01-a570-dc57-c6350c8eafc9.png)

##4.2. Memory Profileの有効化
 プロファイルを行うためにMalloc Profilerを有効にします。

<font color="Red">**2020/04/28 追記**
**「以前のやり方」だとEditorでもProfilerが動作してしまい使い辛いので、こちらがおススメです。**</font>

有効化するためには[Project].Target.csに以下のように*bUseMallocProfiler=true*を追加してプロジェクトのビルドを実行します。

```cs:
public class TP_424Target : TargetRules
{
	public TP_424Target(TargetInfo Target) : base(Target)
	{
		Type = TargetType.Game;
		DefaultBuildSettings = BuildSettingsVersion.V2;

		if (Target.Platform == UnrealTargetPlatform.Win64)
		{
			bUseMallocProfiler = true;
		}
		ExtraModuleNames.AddRange( new string[] { "TP_424" } );
	}
}

```

**以前のやり方**

有効化するためには、BuildConfiguration.xmlに以下のようにbUseMallocProfilerを有効にするための設定を追加してプロジェクトのリビルドを実行します。

```xml:\Engine\Saved\UnrealBuildTool\BuildConfiguration.xml
<?xml version="1.0" encoding="utf-8" ?>
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
  <BuildConfiguration>
	  <bUseMallocProfiler>true</bUseMallocProfiler>
  </BuildConfiguration>
</Configuration>
```
 下記は上記の設定と同じであるため、既に上記を実施してビルドを実行している場合は設定する必要はありません。

```cs:\Engine\Source\Programs\UnrealBuildTool\Configuration\TargetRules.cs

		/// <summary>
		/// If true, then enable memory profiling in the build (defines USE_MALLOC_PROFILER=1 and forces bOmitFramePointers=false).
		/// </summary>
		[XmlConfigFile(Category = "BuildConfiguration")]
		public bool bUseMallocProfiler = true;
```
##4.3. パッケージの作成
 Project Settings の**Include Debug Filesを有効** に設定します。ここで有効にしておかないと、取得したプロファイルをツールで開いてもコールツリーに関数の情報（シンボル情報）が表示されません。
![2022-02-15_12h10_40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101019/a67a9755-cac6-d078-a0f1-ce8e5e4ba4e3.png)


##4.4. パッケージの作成
 ゲームのフルビルドが完了したら、後はパッケージを作成すればプロファイルの準備が完了です。

~~**※Editorの起動時間がかかるようになっているかもしれません。これはEditorの起動時にもMalloc Profileが動作しているためです。[Game]/Saved/Profilingを見るとプロファイル(.mprofファイル)が作成されていることが確認できます。そして膨大なメモリをプロファイリング実行しています。本来はEditorからではなくInstalledBuildなどで作成して自動化するべきですが、残念ならEditorで実行する場合はEngineをカスタマイズしない限りこのようにプロファイルが作成されてしまいます。**~~

<font color="Red">**2020/04/28 追記**
**※4.2.章で追記した内容(BuildConfigurationでの定義ではなくTarget.cs)での定義とすれば、パッケージのみでMallocProfilerを動作させることができます。**</font>

#5. プロファイル
 プロファイルはアプリケーションを起動すると直ぐに開始します。そしてアプリケーションを終了するとプロファイルを終了します。基本的なプロファイルの開始と終了はこの2点です。プロファイルを終了すると[Game]/Saved/Profilingにプロファイル(.mprofファイル)が書き出されます。Memory Profiler中はゲームがプロファイリングの負荷によって低速になります。
![2018-12-16_18h21_07.png](https://qiita-image-store.s3.amazonaws.com/0/101019/f4d08300-a5d1-e292-d24c-d8e310052c43.png)

 「プロファイリングの開始」は**MallocProfilerが生成されたタイミングのみしか存在しない**ため、1度しか開始を実行できません。つまり、**1度プロファイルを停止してしまうと再開できない**ということで。プロファイリングの終了はアプリケーションの終了以外にコンソールコマンドによって特定のタイミングで停止することができます。これ以外にも、レベルの遷移時やコンソールコマンドからプロファイルの中間ポイントをマーキングすることができます。

|Command		|機能							|
|:---------------------	|:---------------------------------------------------	|
| Mprof Start		|プロファイル開始用のコマンド (効果はありません)		|
| Mprof Stop		|プロファイル終了用のコマンド (1度終了すると再開できません)	|
| Mprof Mark [MARK_NAME]|プロファイルの中間ポイントを記録します			|
| DumpAllocStoFile	|Mprof Stopと同じ					|
| SnapshotMemory	|Mprof Markと同じ					|


#6. プロファイルの解析
　プロファイルを取得したら、後はプロファイラで解析するだけです。以降にMemory Profiler2での解析について説明します。

##6.1. Memory Profiler2でのロードと解析
 Memory Profiler2から保存された.mprofファイルを開きます。これには時間がかかる(約1分程度)ことがあります。
![2018-12-16_16h18_54.png](https://qiita-image-store.s3.amazonaws.com/0/101019/2be3dd22-2146-92c9-f69c-aebbb8fe5a29.png)
プロファイルをロードしても直ぐには反映されません。各タブに移動した後、画面右上にある「GO」ボタンを押すことで反映されます。
![2018-12-16_16h14_39.png](https://qiita-image-store.s3.amazonaws.com/0/101019/b4bafa09-22a0-0d6d-e142-6a304966dfc3.png)
特定のメモリリークを見つけ出すにはDiff StartとDiff Endを調整してプロファイルされた期間を調整します。ここには実行時に追加した Markerを選択することができます。もしくは特定のイベント時(マップの遷移時、GC時など)に自動的に生成されたMarkerを選択することも可能です。

![2018-12-16_15h49_26.png](https://qiita-image-store.s3.amazonaws.com/0/101019/649f7a0b-cee9-6966-2aed-88b41e045d09.png)

##6.2. リークの解析
 例えば以下のようにメモリが増加しているような場合、どこで何が追加されたのかを追跡します。これはアプリケーション起動からThirdPersonTemplateのMapが起動するまでのトラッキングの記録なので、当然レベルが生成された際に追加されたメモリが追加されます。
![2018-12-16_20h04_09.png](https://qiita-image-store.s3.amazonaws.com/0/101019/281699c8-9e96-0083-ec80-0571f42a5e14.png)
Details ViewのDiffを見るとどの程度のメモリが増加したかを確認できます。Diffはアプリケーション起動時からの差分なので、当然追加された分がDiffに記録されています。
![2018-12-16_20h05_39.png](https://qiita-image-store.s3.amazonaws.com/0/101019/c6e4153d-1ee6-164e-2fec-716e1cd35552.png)
Execulsive Viewを見ると、どのようなメモリがどこで追加されたかを確認できます。このように、得られた結果が想定されたものか、不用意に増えていないかなどを確認し、リークしている箇所を特定します。
![2018-12-16_20h08_52.png](https://qiita-image-store.s3.amazonaws.com/0/101019/2a6658ed-1c76-223a-43b4-0485850af5f2.png)


#7. まとめ
 Memory Profilerは便利なツールですが、使用条件が限られる点や準備に時間がかかる点などがあるツールです。しかしながら、メモリのトラッキングを正確にPickupでき、また専用のツールを持たないWindowsなどでは十分に役に立つツールです。メモリトラッキングが必要なケースは大抵の場合が開発の終盤（エージングテストなど）であるため、予めどのようなツールか触れておくのも有事の際に便利です。