docs.unity3d.com
    目次を表示する/隠す

    メモリ管理

    Addressables システムは、ロードする各アイテムの参照カウントを記録して、アセットとバンドルのロードに使用するメモリを管理します。

    Addressable アセットがロードされると、その参照カウントが 1 増加し、アセットが解放されると、その参照カウントが 1 減少します。Addressable アセットの参照カウントがゼロに戻ると、そのアセットはアンロード可能になります。Addressable アセットを明示的にロードしたら、それが不要になったときに解放する必要があります。

    "メモリリーク" (不要になった後もメモリ内に残っているアセット) を回避するための基本的な経験則は、ロード関数のそれぞれの呼び出しを、解放関数の呼び出しと対応付けることです。アセットの解放は、そのアセットインスタンス自体への参照か、元のロード操作から返される結果ハンドルを使用して行うことができます。

    ただし、解放されたアセットは、必ずしもメモリからすぐにアンロードされるわけではありません。アセットで使用されているメモリは、そのアセットが含まれている AssetBundle もアンロードされるまで解放されません (解放されたアセットは、Resources.UnloadUnusedAssets を呼び出してアンロードすることもできますが、この操作には時間がかかり、フレームレートに悪影響を及ぼす可能性があります)。

    AssetBundle には、それ自体の参照カウントがあります (システムでは、依存関係としてアセットを含む Addressable アセットのように扱われます)。バンドルからアセットをロードすると、バンドルの参照カウントが増加し、アセットを解放すると、バンドルの参照カウントが減少します。バンドルの参照カウントがゼロに戻ると、含まれているアセットがいずれも使用中でなくなったことになり、バンドルとそれに含まれるすべてのアセットがメモリからアンロードされます。

    ランタイムメモリ管理を監視するには、[イベントビューアー] を使用します。このビューアーには、アセットとその依存関係がいつロードされ、いつアンロードされたかが表示されます。

    メモリが消去されるタイミング

    アセットが参照されなくなった ([イベントビューアー] の青いセクションの下部に示される) 場合でも、アセットがアンロードされているとは限りません。該当するシナリオとしてよくあるのは、1 つの AssetBundle 内に複数のアセットが含まれている場合です。以下に例を示します。

    • 1 つの AssetBundle (stuff) に 3 つのアセット (tree、tank、cow) が含まれています。
    • tree がロードされると、プロファイラーには、tree の参照カウントが 1 として、および stuff の参照カウントが 1 として表示されます。
    • その後、tank がロードされると、プロファイラーには、tree と tank の参照カウントがどちらも 1 として、stuff AssetBundle の参照カウントが 2 として表示されます。
    • tree を解放すると、その参照カウントはゼロになり、青いバーが表示されなくなります。

    この例では、tree アセットは実際にはこの時点でアンロードされません。AssetBundle またはそのコンテンツの一部をロードすることはできますが、AssetBundle を部分的にアンロードすることはできません。stuff 内のアセットはいずれも、この AssetBundle 自体が完全にアンロードされるまでアンロードされません。ただし、エンジンインターフェースの Resources.UnloadUnusedAssets はこのルールの例外となります。上記のシナリオでこのメソッドを実行すると、tree がアンロードされます。Addressables システムはこれらのイベントを認識できないため、プロファイラーのグラフには、Addressable アセットの参照カウントだけが反映されます (メモリの内容を正確に表しているわけではありません)。Resources.UnloadUnusedAssets を使用する場合は、時間のかかる操作であることと、障害の発生しない画面 (ロード画面など) での呼び出しに限定する必要があることに注意してください。

    アセットチャーンの回避

    AssetBundle で最後のアイテムとなったオブジェクトを解放し、直後にそのアセットまたはバンドル内の別のアセットを再ロードすると、アセットチャーン (アセットの頻繁なロード/リロード) という問題が発生する可能性があります。

    例えば、boat と plane という 2 つのマテリアルがあり、その共通のテクスチャである cammo が、それ自体の AssetBundle に入れられているとします。レベル 1 では boat が使用され、レベル 2 では plane が使用されます。レベル 1 が終了したら boat を解放し、その直後に plane をロードします。boat を解放すると、Addressables によってテクスチャ cammo がアンロードされます。その後 plane をロードすると、すぐに cammo が再ロードされます。

    [イベントビューアー] を使用すると、アセットのロードとアンロードを監視してアセットチャーンを検出できます。

    AssetBundle のメモリオーバーヘッド

    AssetBundle をロードすると、バンドルの内部データを格納するためのメモリが Unity によって割り当てられます。このデータは、メモリの他に、それを含むアセットのためにも使用されます。ロードされた AssetBundle の内部データには、主に以下のタイプがあります。

    • ロードキャッシュ: AssetBundle ファイルの最近アクセスされたページを格納します。そのサイズを制御するには、AssetBundle.memoryBudgetKB を使用します。
    • [TypeTree]: オブジェクトのシリアル化レイアウトを定義します。
    • [コンテンツ表]: バンドル内のアセットのリストです。
    • [プリロード表]: 各アセットの依存関係のリストです。

    Addressable グループと AssetBundle を編成するときは、通常、作成してロードする AssetBundle のサイズと数の間のトレードオフを検討する必要があります。バンドルの数が少なく、サイズが大きい場合は、AssetBundle によるメモリ使用量の合計を最小限にすることができます。逆に、バンドルの数が多く、サイズが小さい場合は、アセットと AssetBundle のアンロードが容易になるため、ピーク時のメモリ使用量を最小限にすることができます。

    ディスク上の AssetBundle のサイズは、そのランタイム時のサイズと同じではありませんが、ディスク上のサイズを目安にして、ビルド時の AssetBundle のメモリオーバーヘッドを見積もることができます。AssetBundle を分析するために使用できるバンドルサイズおよびその他の情報は、[Build Layout レポート] から取得できます。

    以降のセクションでは、AssetBundle が使用する内部データと、可能な場合は、それらに必要なメモリの量を最小化する方法について説明します。

    TypeTree

    TypeTree は、1 つのデータタイプのフィールドレイアウトを表します。

    AssetBundle 内の各シリアル化ファイルには、ファイル内のオブジェクトタイプごとに TypeTree が含まれます。TypeTree 情報によって、シリアル化されたときとは少し異なる方法でデシリアライズされるオブジェクトをロードできます。TypeTree 情報は AssetBundle 間で共有されません。各バンドルには、その中に含まれているオブジェクトの TypeTree 一式が格納されています。

    AssetBundle がロードされると、すべての TypeTree がロードされ、AssetBundle の生存期間中はメモリに保持されます。TypeTree に関連付けられたメモリオーバーヘッドの量は、シリアル化ファイルに含まれている固有のタイプの数と、それらのタイプの複雑さに比例します。

    AssetBundle の TypeTree のメモリ要件は、以下の方法で削減できます。

    • 同じタイプのアセットは同じバンドルにまとめます。
    • TypeTree をオフにします。TypeTrees をオフにすると、TypeTree の情報がバンドルから除外され、AssetBundle のサイズが小さくなります。ただし、TypeTree 情報がないと、新しいバージョンの Unity で以前のバンドルをロードするときや、プロジェクトでわずかでもスクリプトを変更した後に、シリアル化エラーまたは未定義の動作が発生する場合があります。
    • TypeTree の複雑さを緩和するために、より単純なデータタイプを選択するようにします。

    TypeTree が AssetBundle のサイズに与える影響を調べるには、TypeTree を無効にしたバンドルと有効にしたバンドルをビルドし、それらのサイズを比較します。AssetBundle で TypeTree を無効にするには、BuildAssetBundleOptions.DisableWriteTypeTree を使用します。ただし、すべてのプラットフォームで TypeTree がサポートされるわけではありません。一方、TypeTree を必要とするプラットフォームもあります (その場合、この設定は無視されます)。

    プロジェクトで TypeTree を無効にした場合は、新しいプレイヤーをビルドする前に、必ずローカル Addressable グループを再ビルドしてください。コンテンツをリモート配布している場合は、現在のプレイヤーの作成に使用したものと同じバージョン (パッチ番号を含む) の Unity のみを使用してコンテンツを更新します。また、わずかでもコード変更は加えないでください (複数のプレイヤーバージョン、更新、Unity のバージョンが混在していると、TypeTree を無効にしても、その手間に見合うほどメモリが節約されない場合があります)。

    コンテンツ表

    コンテンツ表は、バンドル内に含まれているマップで、明示的に加えられた各アセットを名前で検索できるようにするものです。表のサイズは、アセットの数と、マッピングに使用される名前文字列の長さに直線的に比例します。

    コンテンツ表のデータのサイズは、アセットの総数に基づいています。特定の時点でロードされる AssetBundle の数を最小限にすると、コンテンツ表のデータを保持するために使用されるメモリの量を最小化できます。

    プリロード表

    プリロード表は、アセットが参照する他のすべてのオブジェクトのリストです。Unity では、AssetBundle からアセットをロードするときに、プリロード表を使用してそれらの参照先オブジェクトをロードします。

    例えば、プレハブには、その各コンポーネントと、参照する他のアセット (マテリアル、テクスチャなど) のプリロードエントリーがあります。各プリロードエントリーは 64 ビットであり、他の AssetBundle 内のオブジェクトを参照する可能性があります。

    あるアセットが別のアセットを参照し、そのアセットがさらに別のアセットを参照する場合は、両方のアセットをロードするために必要なエントリーがプリロード表に含まれるため、表のサイズが大きくなる可能性があります。2 つのアセットが両方とも 3 つ目のアセットを参照する場合は、両方のアセットのプリロード表に、3 つ目のアセットをロードするためのエントリーが含まれます (参照先アセットが Addressable であるかどうかと、同じ AssetBundle に含まれるかどうかには関係しません)。

    例として、AssetBundle に 2 つのアセット (PrefabA と PrefabB) があり、これらのプレハブの両方が、もう 1 つのプレハブ (PrefabC) を参照する状況を考えてみます。サイズの大きいこのプレハブは、複数のコンポーネントを含み、他のアセットを参照します。この AssetBundle には 2 つのプリロード表が含まれ、1 つは PrefabA 用で、もう 1 つは PrefabB 用です。これらの表には、それぞれのプレハブのすべてのオブジェクトのエントリーが含まれるだけでなく、PrefabC 内のすべてのオブジェクトと、PrefabC が参照するあらゆるオブジェクトのエントリーも含まれます。したがって、PrefabC をロードするために必要な情報は、PrefabA と PrefabB の両方で重複する結果になります。これは、PrefabC が明示的に AssetBundle に加えられたかどうかにかかわらず発生します。

    アセットの編成方法によっては、AssetBundle 内のプリロード表のサイズが大きくなり、多くの重複エントリーが含まれる可能性があります。これは特に、ロード可能な複数のアセットがあり、そのすべてが、上記の状況での PrefabC のような複雑なアセットを参照する場合に当てはまります。プリロード表によるメモリオーバーヘッドが問題になる場合は、ロード時の複雑な依存関係を減らすようにロード可能なアセットを構成すると改善できます。

    AssetBundle 依存関係のロードがメモリに及ぼす影響

    Addressable アセットのロードでは、その依存関係を含むすべての AssetBundle もロードされます。AssetBundle 依存関係は、あるバンドル内のアセットが、別のバンドル内のアセットを参照するときに発生します。この例の 1 つに、テクスチャを参照するマテリアルがあります。詳細については、[アセットと AssetBundle の依存関係] を参照してください。

    Addressables では、バンドル間の依存関係がバンドルレベルで計算されます。あるアセットが別のバンドル内のオブジェクトを参照している場合は、そのアセットのバンドル全体が別のバンドルに依存することになります。つまり、最初のバンドルからアセットをロードすると、そのアセット自体には依存関係がなくても、2 番目の AssetBundle もメモリにロードされます。

    以下に例を示します。

    BundleA には、Addressable アセット RootAsset1 および RootAsset2 が含まれています。RootAsset2 は、BundleB に含まれている DependencyAsset3 を参照します。RootAsset1 は BundleB を参照しませんが、それでも BundleB は RootAsset1 の依存関係となります。これは、RootAsset1 が、BundleB を参照する BundleA に含まれているためです。

    Note

    Addressables 1.13.0 より前のバージョンでは、依存関係グラフが現在ほど正確ではありません。上記の例では、RootAsset1 が BundleB に依存していることが認識されません。この以前の動作では、別の AssetBundle から参照されている AssetBundle がアンロードされて再ロードされたときに、参照の問題が発生していました。この修正の結果、依存関係グラフが複雑な場合に追加のデータがメモリに残ることがあります。

    必要以上のバンドルのロードを回避するために、AssetBundle 間の依存関係は、可能な限り単純に保つように努める必要があります。Addressables には、これを実現するために使用できる診断ツールがいくつか含まれています。

    • [Analyze ツール]
    • [Build Layout レポート]
    トップに戻る
    Copyright © 2023 Unity Technologies — 商標と利用規約
    • 法律関連
    • プライバシーポリシー
    • クッキー
    • 私の個人情報を販売または共有しない
    • Your Privacy Choices (Cookie Settings)