Search Unity

最適化の最前線から:Addressables を使ってメモリを節約する

, 3月 31, 2021

メモリ周辺でアセットの効率的にストリーミングさせることは、どのようなクオリティのゲームにおいても重要な要素です。私はプロフェッショナルサービスチームのコンサルタントとして、多くの顧客のプロジェクトのパフォーマンスの向上に努めてきました。その経験をもとに、Unity の Addressable アセットシステムを活用してコンテンツ読み込み時の戦略を改善する方法に関するヒントをいくつかご紹介したいと思います。

メモリは注意深く管理しなければならない希少なリソースであり、プロジェクトを新しいプラットフォームに移植するときには特に気を配る必要があります。Addressables を使用すると、弱参照を利用して不要なアセットが読み込まれないようにして、実行時のメモリの使用効率を向上させることができます。弱参照を使うと、参照されるアセットがメモリに読み込まれるタイミングとメモリから削除されるタイミングを制御することができます。Addressables システムは、必要なすべての依存関係を見つけてそれらを読み込むこともします。

このブログでは、Unity の Addressable アセットシステムを使うようにプロジェクトを設定する際、発生する可能性のあるいくつかのシナリオと問題について説明し、それらを認識して迅速に修正する方法について説明します。

ゲーム内の持ち物を扱うサンプル


これからご説明する一連の推奨事項を説明するにあたり、以下のような設定の単純なサンプルを用います。

  • シーンには、Sword、Boss Sword、Shield プレハブの 3 つの持ち物のアセットへの参照を含む InventoryManager スクリプトがある
  • これらのアセットは、ゲームプレイ中に常に必要なわけではない

このサンプルのプロジェクトファイルは、筆者個人の GitHub レポジトリからダウンロードできます。

実行時のメモリ使用状況を表示するために、プレビュー版の Memory Profiler パッケージを使用しています。Unity 2020 LTS では、パッケージマネージャーからこのパッケージをインストールする前に、まずプロジェクト設定でプレビュー版パッケージを有効にする必要があります。

Unity 2021.1 を使用している場合は、パッケージマネージャーウィンドウの追加メニュー(+ ボタン)から Add package by name オプションを選択します。そこで「com.unity.memoryprofiler」という名前を入力してください。

ステージ 1:強参照、Addressables なし

最も基本的な実装から始めて、Addressables コンテンツを設定するための最良のアプローチに近づけていきましょう。最初はシーンに存在する MonoBehaviour のプレハブに強参照(インスペクターでの直接割り当て、GUID によって追跡)を適用しただけのシンプルな設定です。

シーンが読み込まれると、シーン内のすべてのオブジェクトがそれらの依存関係とともにメモリに読み込まれます。これは、InventorySystem にリストされているすべてのプレハブが、それらのプレハブのすべての依存関係(テクスチャ、メッシュ、オーディオなど)とともにメモリに存在することを意味します。

ビルドを作成し、メモリプロファイラーを使用してスナップショットを作成すると、アセットのテクスチャがインスタンス化されていなくても、アセットのテクスチャがすでにメモリに保存されていることがわかります。

アイテムのテクスチャが、まだインスタンス化されていないのにメモリに保存されている。

問題:現在必要のないアセットがメモリにある。持ち物になるアイテムが多数あるプロジェクトでは、これが実行時のメモリをひどく圧迫する原因になる。

ステージ 2:Addressables を実装する

不要なアセットの読み込みを回避するために、Addressables を使用するように持ち物システムを変更します。直接参照の代わりにアセット参照を使用すると、これらのオブジェクトがシーンと一緒に読み込まれるのを防ぐことができます。持ち物のプレハブを Addressables グループに移動し、InventorySystem を変更して、Addressables の API を使用してオブジェクトをインスタンス化および破棄します。

プレイヤーをビルドし、スナップショットを取ります。アセットがまだメモリに読み込まれていないことに注意してください。これで、インスタンス化されていないならメモリに読み込まれないようになったということです。

持ち物になるアイテムのテクスチャはメモリに表示されず、TextMeshPro テクスチャのみが表示されている。

すべてのアイテムをインスタンス化して、アセットがメモリ内にある状態で正しく表示されることを確認します。

問題:すべてのアイテムをインスタンス化して Boss Sword を破棄した時、使用されていないはずの Boss Sword のテクスチャ「BossSword_E」がメモリに表示されている。これが起きる原因は、アセットバンドルを部分的に読み込むことはできるが、自動的かつ部分的に破棄することはできないことである。このような挙動は、すべての持ち物のプレハブを含む単一のアセットバンドルなど、多くのアセットが含まれるバンドルを使う時、特に問題になる可能性がある。アセットバンドル全体が不要になるまで、またはコストのかかる CPU 命令である Resources.UnloadUnusedAssets() を呼び出すまで、バンドル内のどのアセットも破棄されない。

テクスチャ BossSword_E は、Boss Sword が破棄された後もメモリに残っている。

 

ステージ 3:バンドルを小さくする

この問題を修正するには、アセットバンドルの整理方法を変える必要があります。現在、すべてのアセットを 1 つのアセットバンドルにパックする Addressables グループが 1 つだけありますが、プレハブごとにアセットバンドルを作成することもできます。細かくアセットバンドル化していくことで、大きなバンドルを使っていると不要になったアセットまでメモリに残ってしまう問題を軽減できます。

この変更は簡単です。Addressables グループを選択し、Content Packaging & Loading > Advanced Options > Bundle Mode を選択し、インスペクターに移動して、Bundle Mode を Pack Together から Pack Separately に変更します。

Pack Separately を使用してこの Addressables グループを構築することにより、Addressables グループ内のアセットごとにアセットバンドルを作成できます。

アセットとバンドルは次のようになります。

ここで、元のテストに戻ります。3 つのアイテムをスポーンしてから、Boss Sword を破棄しても、不要なアセットがメモリに残ることはなくなりました。バンドル全体が不要になったため、Boss Sword のテクスチャが破棄されるようになりました。

問題:3 つすべてのアイテムをスポーンし、メモリキャプチャを取得すると、重複するアセットがメモリに表示される。具体的には、テクスチャ「Sword_N」と「Sword_D」の複数のコピーが作成されている。バンドルの数だけを変更したとして、これはどのように発生するのだろうか。

ステージ 4:重複するアセットを修正する

この問いに答えるために、私たちが作成した 3 つのバンドルに含まれるすべてのものについて考えてみましょう。3 つのプレハブアセットのみをバンドルに入れましたが、プレハブの依存関係として暗黙的にそれらのバンドルに取り込まれる追加のアセットがあります。たとえば、Sword のプレハブアセットには、含める必要のあるメッシュ、マテリアル、テクスチャのアセットもあります。これらの依存先が Addressables の他の場所に明示的に含まれていない場合、それらを必要とする各バンドルに自動的に追加されます。

Sword と Boss Sword のアセットバンドルには、同じ依存関係がいくつか含まれている。

Addressables には、バンドルのレイアウトの診断に役立つ分析ウィンドウがあります。メニューを Window > Asset Management > Addressables > Analyze と開き、ルール Bundle Layout Preview を実行します。ここでは、Sword バンドルに sword.prefab が明示的に含まれていることがわかりますが、このバンドルには多くの暗黙的な依存関係も含まれています。

同じウィンドウで、Check Duplicate Bundle Dependencies を実行します。このルールは、現在の Addressables レイアウトに基づいて、複数のアセットバンドルに含まれるアセットを強調表示します。

分析によると、Sword バンドルの間に重複したテクスチャとメッシュがあり、3 つのバンドルすべてが同じシェーダーを複製していることがわかる。

これらのアセットの重複を防ぐには、以下の 2 つの方法があります。

  1. Sword、Boss Sword、Shield のプレハブを同じバンドルに配置して、依存関係を共有する
  2. 重複したアセットを Addressables のどこかに明示的に含める

不要なアセットがメモリに残るのを防ぐために、同じバンドルに複数の持ち物のプレハブを配置することは避けたいと考えています。そのため、複製したアセットを専用のバンドル(Bundle 4 と Bundle 5)に追加します。

重複していたテクスチャを、専用のバンドルに明示的に配置する。

バンドルの分析に加えて、分析ルールは、Fix Selected Rules を使って問題のあるアセットを自動的に修正できます。このボタンを押して、「Duplicate Asset Isolation」という名前の新しい Addressables グループを作成します。このグループには、4 つの複製されたアセットが含まれています。このグループの Bundle Mode を Pack Separately に設定して、不要になった他のアセットがメモリに保持されないようにします。

ステージ 5:大規模プロジェクトでアセットバンドルのメタデータサイズを削減する

このアセットバンドル戦略を使用すると、大規模な問題が発生する可能性があります。特定の時間にロードされたアセットバンドルごとに、アセットバンドルメタデータのメモリオーバーヘッドがあります。この現在の戦略を数百または数千の持ち物アイテムにスケールアップすると、このメタデータは許容できない量のメモリを消費する可能性があります。アセットバンドルメタデータの詳細については、Addressables のドキュメントをご覧ください。

Unity プロファイラーで現在のアセットバンドルメタデータによるメモリコストを表示します。メモリモジュールに移動し、メモリスナップショットを撮ります。カテゴリ Other > SerializedFile を見てみましょう。

現在、1,819 個のバンドルが SerializedFile メモリを伴って読み込まれており、その合計サイズは 263MB である。

読み込まれたアセットバンドルごとに、メモリ内に SerializedFile エントリが作られます。このメモリは、バンドル内の実際のアセットではなく、アセットバンドルのメタデータです。このメタデータには次のものが含まれます。

  • 2 つのファイル読み取りバッファー
  • バンドルに含まれるすべての一意のタイプを列挙する型ツリー
  • アセットを指すコンテンツのテーブル

これら 3 つの項目のうち、ファイル読み取りバッファーが最も多くの容量を占めます。これらのバッファーは、PS4、Switch、および Windows RT ではそれぞれ 64KB であり、それ以外のプラットフォームでは 7KB です。上記の例では、1,819 バンドル* 64 KB * 2 バッファーで、バッファーのみで 227MB になります。

バッファーの数はアセットバンドルの数に比例して変化するため、メモリを削減する簡単な解決策は、実行時に読み込まれるバンドルの数を減らすことです。ただし、以前は不要なアセットがメモリに残るのを防ぐために、大きなバンドルの読み込みを回避していました。では、粒度を維持しながらバンドルの数を減らすにはどうすればよいでしょうか。

最初のステップとして手堅いものは、アプリケーションでの使われ方に基づいてアセットをグループ化することです。アプリケーションに基づいて賢明な仮定を立てることができれば、ゲーム本体のステージに基づいたグループに分けたアセットなど、常に一緒に読み込む、あるいは破棄されることがわかっているアセットをグループ化できます。

一方、アセットがいつ必要になるか、あるいは不要になるかについて、安全な仮定ができないこともあります。たとえば、オープンワールドゲームを作成している場合、プレイヤーが森で取ったアイテムを別の森に運ぶ可能性があるため、ある森にあるものすべてを 1 つのアセットバンドルに単純にグループ化することはできません。その場合、プレイヤーはアイテムを取ってきた森で使っているアセットのうち 1 つを必要としているため、森のバンドル全体がメモリに残ります。

幸い、必要なレベルの粒度を維持しながら、バンドルの数を減らす方法があります。バンドルの重複を排除する方法をもっと賢く実行すればいいのです。

私たちが実行した組み込みの重複排除分析ルールは、複数のバンドルに含まれるすべてのアセットを検出し、それらを単一の Addressables グループに効率的に移動します。そのグループを Pack Separatelly に設定すると、バンドルごとに 1 つのアセットになります。ただし、メモリの問題を発生させることなく安全にまとめることができる重複したアセットがいくつかあります。次の図について考えてみましょう。

テクスチャ「Sword_N」と「Sword_D」は同じバンドル(Bundle 1 と Bundle 2)の依存先であることがわかっています。これらのテクスチャは同じ親を持っているため、メモリの問題を引き起こすことなく安全にまとめることができます。両方の剣のテクスチャは常に読み込み、または破棄される必要があります。1 つのテクスチャを使用し、もう 1 つのテクスチャを使用しない場合はないため、テクスチャの 1 つがメモリに残る可能性があるという心配はありません。

この改善された重複排除ロジックは、独自の Addressables 分析ルールに実装できます。既存の CheckForDupeDependencies.cs ルールを元に実装することになります。完全な実装コードは、持ち物システムのサンプルで確認できます。

このシンプルなプロジェクトでは、バンドルの総数を 7 つから 5 つに減らしただけでした。しかし、アプリケーションの Addressables に数百、数千、またはそれ以上の重複アセットがあるシナリオならどうでしょうか。『Subnautica』のプロフェッショナルサービスエンゲージメントで Unknown Worlds Entertainment と協働した時、最初に組み込みの重複排除分析ルールを適用して、合計 8,718 個のバンドルがある状態になりました。その後、バンドルの親に基づいて重複排除されたアセットをグループ化するカスタムルールを適用したところ、バンドルを 5,199 個まで減らすことができました。このケースについて書いたストーリーでは、同チームとの共同作業について詳しく知ることができます。

バンドルの数を 40% 削減しつつ、コンテンツは同一の物を維持し、粒度も同じレベルを維持しました。バンドル数を 40% 削減したことで、同様に実行時の SerializedFile のサイズも 40% 削減(311MB から 184MB)されました。

結論

Addressables を使用すると、メモリ消費を大幅に削減できます。ユースケースに合わせてアセットバンドルを整理することで、メモリをさらに削減できます。結局のところ、組み込みの分析ルールは、すべてのアプリケーションにそれなりに有効に働く代わりに、保守的なものになっています。独自の分析ルールを作成することで、バンドルレイアウトを自動化し、皆さんのアプリケーションに合わせて最適化できます。メモリの問題を検出するには、頻繁にプロファイルを作成し続け、分析ウィンドウをチェックして、バンドルに明示的および暗黙的に含まれているアセットが何であるかを把握することです。

その他のベストプラクティス、使い始めるために役立つガイド、および拡張 API ドキュメントについては、Addressable アセットシステムのドキュメントを確認してください。

Addressables アセットシステムを使用してコンテンツ管理を改善する方法を学ぶための実践的なガイドが必要な場合は、専門的なトレーニングコースとして提供している「Manage Content with the Addressable Asset System」で学ぶと良いでしょう。詳細は弊社の営業担当者までお問い合わせください。現在、このトレーニングは日時を定めたライブ公開セッションとして提供されていますが、近日中にオンデマンドでも利用できるようになる予定です。

22 replies on “最適化の最前線から:Addressables を使ってメモリを節約する”

I swear I’m losing my mind here because a lot of you are both misinterpreting this post dramatically and also convinced that Addressables aren’t one of the better solutions for content memory management in ages across multiple engines. I’d argue that Addressables are a far more important and versatile addition to the engine than the entire data oriented tech stack, while a lot of you are treating it like it’s Enlighten 2.0 or something.

Somebody even said that other game engines have solved this problem, but as somebody who has worked in engines going back to Ogre3D, absolutely not. Content memory management is not a solved problem in any engine aside from in-house ones with specific pipelines often targeting specific games. Unity is not an in-house engine, it is a generalist engine that needs to work with everything from games that run on mobile hardware all the way up to next-gen consoles. Because of this, solutions for content memory management need to be robust and cover a wide variety of use cases. This seems to be a sticking point for a lot of people here.

One person suggested using the resources folder, which is absolutely baffling to me because Unity has been doing everything it can to push people away from the resources folder because it’s slow, dramatically more cumbersome to use than interfacing with Addressables at the C# level, and has a hard size limit of 4gb, rendering it useless for a massive amount of games, even lots of mobile titles.

Another person said that in-editor references have to be replaced, this is COMPLETELY untrue. This is such a misrepresentation of how things work that I’m wondering if the argument was made in good faith in the first place. Yes, you have to replace references if you’re trying to access Addressable content, but I’ll blow your mind with this one neat tip:

Don’t use Addressables for that stuff then. You don’t have to. You’re free. Addressables might not be the best option for your UI or similar things that benefit from direct links like that! So don’t use them! Just like with ECS, this doesn’t have to be and, in fact, is not a total replacement for your entire workflow. What Addressables ARE for is opening up further optimization strategies for people working on games where memory limits and asset streaming are major considerations and a fair few games just looking for improved performance.

What Addressables are NOT are the nightmare that was the old AssetBundle system that was not only more cumbersome to use, suffered even more interface problems, were only modestly faster than the resources folder by default and not even remotely as fast as Addressables when optimized well. I could make the argument that Addressables are the only good core-engine feature Unity has added in ages.

Good to hear a sane voice in the comments!

I always feel that a lot of loud voices in the Unity community tend to not understand how varied much of the professional work done with Unity is. The constant nagging over “why is this feature so difficult” starts to get old. I wish people would read or learn a bit more before posting.

Some of the comments in this post would benefit a whole lot by reading the last part of this chapter were they mention the scale of assets used in Subnautica.

Thank you Unity team for this blog post. Please continue with them!

All of this could be solved with late/lazy loading.

References in inspector having a tick box for lazy/late load ticked aren’t immediately brought into memory, instead they require a call to be loaded, and provide an optional callback once they are.

Much like aSync scenes, only not so convoluted.

No, it really couldn’t, lazy loading like that comes with a whole host of usability and tracking issues that add multiple layers of complexity to any reasonable memory management. A tick box? Cool, great. How do you do it from code? How do you handle lists of things that may need to be loaded at different times under different contexts? How many systems are you making yourself that Addressables currently already has?

“how do you solve… etc” for late/lazy loading?

Follow the tried and tested, true and efficient model of ARC.

Added benefit – warnings that are exact.

EXTRA GAIN: super easy memory clean up options.

Massive PLUS: In editor notification of overlaps and contradictions in efforts to streamline memory

The switch in the Inspector Editor is merely a surfacing of access to this, such that a non-programmer can gain the benefits of optimising memory usage, without needing to read a blog post like the above.

ANY system should be designed for end users, to be as easy to use as possible. The above blog post demonstrates this is something Unity’s not good at.

“Another person said that in-editor references have to be replaced, this is COMPLETELY untrue.” … “Yes, you have to replace references if you’re trying to access Addressable content, but I’ll blow your mind with this one neat tip:” … “Don’t use Addressables for that stuff then.”

So….you meant to say my claim is COMPLETELY true. Sorry your feature has big problems. Doesn’t make it any nicer to use.

Behind the scenes the system has a lot of great stuff. The user facing interface needs a complete overhaul. Unity has a history of refitting flawing systems, like UNET, so hopefully they get the message here.

Yeah maybe it’s better than OGRE and asset bundles, but that doesn’t make it good. The basic interface shouldn’t be any more difficult than manifests of assets that are commutative between editor, addressable, and script.

Not being a one size fit all solution is not a problem at all, let alone a “big problem.”

And I didn’t say I ONLY used Ogre. I’ve also used UDK, UE4, Lumberyard, CryEngine (go here if you want to see a real disaster), and even Gamebryo. Addressables is a far more usable asset memory management solution than anything they over and covers more use cases.

Addressables are a half baked solution – the worst user facing system I’ve seen from unity – even worse than UNET. They are also basically required if you want to have any reasonable control over your game’s memory.

The interface is totally ham fisted – to use it you will have to change the way your program interacts with basic gameobects and prefabs. Simple in editor references have to be replaced to use this system, and there is no built in way to map between asset references and monobehaviours.

Just take a look at the forums to see the pain that awaits. Other game engines have solved this problem, at least passably, for years – this solution is just a bear to work with. Good luck.

It’s not. It’s covering a variety of use cases that will change depending on the size of your game and availability of memory as a resource, and what the best practices in those situations is. This is an in-depth post about the various features of a toolchain, not Intro to Addressables. The third sentence using the term “content loading strategy” should be a giveaway.

This is an intermediate level subject being taught like one.

Addressables may not be the right tool for everyone, but if you decide it’s a solution you’d like to adopt then I strongly encourage you to share any questions or challenges you encounter on our Addressables forums: https://forum.unity.com/forums/addressables.156/

The community and are developers are quite active in that space, and many questions or challenges you face may have already been solved. :)

Funny how the super simple example here would be best served by just using the Resources folder and 2 line of code, instead of using an overengineered and over-complicated solution like addressables.

I like how in a simple blog post on how to solve a fairly simple problem (load asset A or B depending on some factors), you have a multi step blog post, and some of the steps are about solving problems that are inherent to using addressables.

Thanks Unity, because when I want to solve a problem, I actually want to solve 10 problems.

Addressables : Turn your 1 problem into 10 problems.

…what?

The resources folder is not only a terrible solution when dealing with any meaningfully sized project, but it’s also exceptionally slow. Have you actually spent any amount of time using addressables, or are you just complaining about a post?

The answers to your question are a google search away. Nick is a prolific user and recorder of his experiences with unity, over a very long time, and in great depth. He and his dev partner have been huge helpers and providers of profound insights into unity usage.

Deeds: why would I google some random name attached to a comment? Also this does not answer my question about addressable use specifically, which does not match any of the experiences I’ve had with addressables in ages.

niko: the most recent post in that thread is from nearly a year ago

“why should you use google?”

Because nobody else is going to answer your questions when they’re phrased in such a charming manner.

Don’t get it twisted. I asked why I should google a random person’s name attached to a comment, something that people don’t tend to do in general, especially when Nick’s response doesn’t actually address anything other than a perceived system-level overcomplexity based on a blog post being misinterpreted as a an Intro course and not best practices for a variety of situations

I have heard similar things from other experienced developers.

The amount of steps detailed in this article makes it seem that addressables increase complexity by a whole lot.

Except this is covering multiple use cases and even explains the contexts in which you might need to exercise certain practices. This is one of the first good blog posts here in absolute ages because it’s actually covering best use case scenarios and resource management.

Because when you teach/showcase something, you start with the most complex situation… But then you’re just a horrible teacher.
Your argument is not really useful here.

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です