【Unity】メモリリークを分析するための簡単で強力な方法

AvatarPosted by

こんにちは。
クライアントエンジニアの二間瀬です。
先日、あるプロダクトの最適化の一貫でメモリリークの調査に携わる機会があったため、今回はその際に行ったUnityのメモリリーク調査の方法についてお話したいと思います。

メモリリークしていると、ゲームの動作が遅くなったりクラッシュする可能性があるため、回避する必要があります。
メモリリークは多くの場合、以下が原因で起こり得ます。

  • プログラムで解放や破棄する処理を記述していない
  • オブジェクトが意図せず参照され続けている

この2つの場合の問題箇所の特定方法について見ていきます。

なお、今回の検証で使用した環境は以下のようになっています。

  • Unity 2020.2.1f1
  • Memory Profiler 0.2.8-preview.2
  • Heap Explore 3.6.0

解放されていない不必要なメモリを探す

まず、UnityのMemory Profilerを使用してリーク箇所を特定する方法を紹介します。
なお、Profilerを使用しても同様に調べることができますが、TileMapでより直観的に調べられるため個人的にはMemory Profilerの方が調べやすいと思います。

Memory ProfilerはPackage Managerを使用してインストールする必要があります。執筆時点ではまだPreview版なので、Preview Packagesを有効にする設定にして検索しないと出てこないので注意が必要です。

MemoryProfilerを使用した調査フロー

MemoryProfilerを使用したメモリリーク調査は以下のフローをたどります。

  1. アセットをロードしている等の怪しそうなシーンやゲーム画面を再生し、スナップショットを取る。
  2. 別のシーンやゲーム画面に移動し、再度スナップショットを取る(場合によっては1.と2.を繰り返して増減を見る)。
  3. 2つのスナップショットを開き、差分をとる(MemoryProfiler左下にあるボタン押下)。

 

 

 

4. FiltersのDiffタブでGroupを選択し、Deleted/New/Sameでグループ化する。

 

 


 

5. Newにグループ化されたものは、2.のスナップショットにのみ存在するオブジェクトであるため、メモリリークの可能性がある。

5.で怪しいオブジェクトを見つけたら、References列の数字をクリックすることで、参照を辿っていくことが出来ます。

static referencedなオブジェクトを調べる

多くの場合、メモリリークは上記の手順で発見および解決が可能と思われますが、一部の場合で追跡しきれない場合があります。
その一つに、static変数に参照され続けるメモリが挙げられます。
自分の場合、Memory Profilerを見てもReferencesが0でありそれ以上の追跡ができなかったリソースが、static変数によって参照されていたというケースがありました。

static変数はゲームが起動している間は破棄されないため、本来不要な場面でもメモリに載ってしまっているといった事態が生じ得ます。
可能ならこれも修正したいため、次はこうしたメモリがどの変数から参照されているかを追跡する手段について見ていきます。

ツールはMemoryProfilerに代わり、Heap Exploreという解析ツールを使用します。

インストール

Unity 2019.3以降なら、Heap ExploreのGitHubにあるPackageURLsからURLをコピーし、PackageManagerの”add package from git url”に入力することでインストールすることが出来ます。

Heap Exploreを使用した調査フロー

スナップショットをとる、2つのスナップショットの差分を表示するなど、基本的な機能はMemory Profilerと同様のものが提供されています。
そのため、リーク箇所の特定までの手順は上記を踏襲することが出来ます。

原因箇所のあたりを付けたら、今回はUnityメモリを調べたいので、Overview画面から「Native Memory Usage」グループのInvestigateボタンを押して、詳細画面にいきます。


詳細画面ではすでに型ごとにグループ化されているので探しやすくなっています。

今回は、以下のクラスを使用したシーンでプロファイリングを試してみます。

using UnityEngine;

public class DemoClass : SingletonMonoBehaviour<DemoClass>
{
    public Texture tex;
    private static RenderTexture SRT;
    
    void Start()
    {
        SRT = new RenderTexture(1280, 1280, 0);
        SRT.name = "SRT";
        Graphics.Blit(tex, SRT);
    }
}

RenderTextureのオブジェクトをクリックすると、下図のように、右下のPath to Root パネルに参照情報が表示されます。
Type内に緑色のC#ボタンがあるのでクリックすると、参照しているC#クラスが表示されます。

参照しているクラスや変数がstaticの場合は下図のようにSアイコンが表示されます。
確かに参照情報が追跡出来ていることがわかります。

このようにHeapExploreは、MemoryProfilerよりも詳細なプロファイリングが可能となっており、そのほか紹介していない機能も多く搭載している優れたツールとなっています。

余談ですが、UnityのMemoryProfilerよりもHeapExploreの方が各操作が断然高速です。
ビジュアライズが必要ない場合は、初めからHeapExploreを導入すると読み込み中等の待機時間に悩まされなくて済むかもしれません。

おわりに

UnityのMemory Profilerと、Heap Exploreを使用したUnityメモリのリークの調査方法について見ていきました。

ちなみに、自分が調査したメモリリークの原因は、RenderTextureのRelease漏れと、シングルトンなリソースマネージャによる参照でした。
プロダクトの肥大化につれこうしたミスは少なからず起こり得ますが、本記事で紹介した方法がそれらの問題箇所の特定と修正に役立てれば幸いに思います。

参考文献

https://docs.unity3d.com/Packages/com.unity.memoryprofiler@0.2/manual/workflow-memory-leaks.html