メモリの消費状況はパフォーマンスの状態を示す重要な指針です。これは、低性能モバイルデバイスなど、メモリ資源の少ないプラットフォームにおいては特に重要です。
Unity におけるメモリ問題の診断には、 Unity の Bitbucket から入手可能なオープンソースのメモリ視覚化ツール が最適です。このツールの統合は簡単で、リンクのデポジトリをダウンロードし、そこに含まれる Editor フォルダーをプロジェクトに配置するだけで行えます。
このツールは、 5.3 以降のどのバージョンの Unity でも使用できます。 IL2CPP を用いてビルドされたアプリケーションに添付することで、ネイティブコードおよびマネージドコードのメモリ消費に関する豊富な情報を取得することができます。
このツールを使用するには、 IL2CPP スクリプトバックエンドでプロジェクトをビルドし、それを適切なデバイスにデプロイします。 津上の Unity のエディター内 CPU プロファイラーを添付し、(メニューを Window > MemoryProfilerWindow と進んで) Memory Profiler ウィンドウを開き、 Take Snapshot を選択してください。
データが収集されて Unity エディターに送信される間、デバイス上のアプリケーションが少しの間一時停止します。その後 Unity エディターが、受領したデータを解析するために一時停止しますが、これに著しく時間が掛かる場合があります。メモリ負荷が特に高いアプリケーションの場合、トレースの解析に 10~30 分掛かることもあります。
この解析・読み込み処理が行われる間、しばらくお待ちください。
このスクリーンショットは iOS デバイスで実行される Standard Assets Scene から取ったものです。消費メモリの 4 分の 3 が、飛行機の機体に関わる 4 つの非常に大きなテクスチャに使用されていることが確認できます。
この表示は拡大して見ることができます。アプリケーション内の各ボックスをクリックすると、詳細情報が表示されます。
メモリの問題でよくあるもののひとつは、メモリ内におけるアセットの重複です。テクスチャはプロジェクト内で最もメモリ負荷の高いアセットであるため、テクスチャの重複は Unity プロジェクトで最も一般的なメモリ問題のひとつとなっています。
重複したアセットは、同じアセットから読み込まれたと思われる同タイプ・同サイズの 2 つのオブジェクトを見付けることで特定することができます。 Memory プロファイラーの Detail のセクションにある Name フィールドと InstanceID フィールドを確認して、同一であると思われるオブジェクトを探してください。
Name フィールドは、オブジェクトの読み込み元であるアセットファイルの名前から来ています。通常これは、ファイルのパスや拡張子なしのファイル名となっています。 InstanceID フィールドは、 Unity ランタイムによって割り当てられた内部的な識別番号を示します。この番号は、 Unity ゲームの 1 回の実行中において固有の番号となります(1)。
こちらの画像で上記の問題の一例を確認できます。左右の画像はそれぞれ、 Unity 5.4 の Memory プロファイラーの Detail セクションのスクリーンショットです。ここには表示されているアセットは、メモリ内に別々に読み込まれた 2 つのテクスチャです。これらのテクスチャは名前とサイズが全く同じなので、重複テクスチャである可能性が推測されます。プロジェクトの “Assets” フォルダーを調べると、 wood-floorboards-texture という名前のアセットが 1 つしかないことが判明するかもしれません。そうであれば、このアセットは重複している可能性が非常に高くなります。
メモリ内の各 UnityEngine.Object はそれぞれ固有のインスタンス ID を持っています。この ID はオブジェクトの作成時に割り当てられるものです。この 2 つのテクスチャは異なる ID を持っているので、それぞれが、(メモリに読み込まれた)別々のテクスチャデータ一式を表しているということが確定できます。
ファイル名とアセットサイズが同じでインスタンス ID が異なるので、これらの 2 つのオブジェクトは、メモリ内で複製された 1 つのテクスチャを表しているということが確かです。(ノート: 同一のファイル名を持つテクスチャがプロジェクト内にある場合は、これは断定できませんが、このようにファイルサイズが全く同じである場合は、その可能性が非常に強いといえます。)
メモリ内でテクスチャやアセットの重複が引き起こされる最もよくある原因は、アセットバンドルの不適切なアンロードです。この問題に関する解説は、 Unity の アセットバンドルに関するベストプラクティスガイド でお読みいただけます。重要なセクションは 読み込み済みアセットの管理 です。
メモリ視覚化ツールでは、イメージエフェクトやレンダーテクスチャ オブジェクトへのレンダーバッファの供給に掛かるメモリを視覚的に確認することもできます。
上のスクリーンショットは、 Unity の Cinematic Image Effect がいくつか適用された単純なシーンから取ったものです。これらのイメージエフェクトは、演算処理を実行するために一時的なレンダーバッファの割り当てを行います。中でも Bloom エフェクトは、徐々にサイズの減少するいくつかのバッファーを割り当てます。 Retina iOS デバイスは高解像度なため、このような一時的バッファの使用メモリが、プロジェクトのその他全ての部分の合計使用メモリを大幅に凌ぐ量となります。
また iPad Air 2 は 2048 x 1536 でレンダリングを行います。これは、最近の据え置き機や PC 向けプロジェクトで一般的に目標とされる解像度 1080p を超えるものでありながら、 タブレット端末で実行されることになります。全画面の一時的レンダーバッファーは、バッファー形式によって、 24 MB または 36 MB のメモリをフルに使用します。レンダーバッファーのピクセル寸法を半分にすると、これを 75% 削減できます。この方法は多くの場合、ビジュアルのクオリティをそれ程下げずに行うことができます。
一時的レンダーバッファやその他 GPU 資源のイメージエフェクトによる使用を最適化する方法のひとつは、様々な計算を全て同時に実行する “Uber” イメージエフェクトを 1 つ作成することです。バージョン 5.5 以降の Unity をお使いの場合は、新しい UberFX (github から入手可能)パッケージの使用が可能です。このパッケージは、 Cinematic Image Effect が提供する全ての処理を、個々の Image Effect を使用するよりも少ないオーバーヘッドで実行することのできる、設定調整可能な “uber(素晴らしい)” イメージエフェクトを提供します。