【Unity】Addressableアセットシステムのメモリ管理の仕組み

UnityのAddressableアセットシステムのメモリ管理の仕組みについてまとめました。

Unity2019.2.10
Addressable1.3.8

はじめに

この記事ではAddressableアセットシステムのメモリ管理の仕組みについてまとめます。
Addressableの概念や基礎知識についての説明はこの記事では省略しますが、
以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

また、ロードやアンロードの基本的なやり方については以下の記事にまとめています。

light11.hatenadiary.com

本記事はこれらの知識を前提とします。

ロードとリリース(アンロード)をペアにするのが基本

さてAddressableは参照カウントでリソースのロード・アンロードを管理しています。
Addressables.LoadAssetAsync()などでロードがリクエストされると参照カウントがインクリメントされ、
Addressables.Release()などでリリース(アンロード)がリクエストされるとデクリメントされます。

// 参照カウントがインクリメントされる
var handle = Addressables.LoadAssetAsync<GameObject>("Example");

// 参照カウントがデクリメントされる
Addressables.Release(handle);

そして参照カウントが1以上になった時にアセットがロードされて、0になった時にアンロードされます。

以上のことから、当然ですが「ロードしたものは必ずリリースする」というのが大原則となります。
ただし明示的にリリースしなくてもUnityが自動でやってくれるケースがあったりするので、このあたりを以下でまとめます。

GameObjectの生成方法とメモリ管理

2つの生成方法

Addressableを使ってGameObjectをインスタンス化する方法は大きく分けて二つあります。

一つ目はAddressables.LoadAssetAsync()などを使ってロードした後にGameObject.Instantiate()をする方法です。

var handle = Addressables.LoadAssetAsync<GameObject>("Example");
await handle.Task;
Instantiate(handle.Result);

また二つ目の方法としてAddressables.InstantiateAsync()を使う方法があります。

var handle = Addressables.InstantiateAsync("Example");
メモリのリリース方法の違い

さて、Addressables.LoadAssetAsync()を使ってロードを行った場合、
Addressables.Release()をすることですべての関連するリソースへの参照カウンタがデクリメントされます。

これに対してAddressables.InstantiateAsync()インスタンス化した場合には、
Addressables.ReleaseInstance()を使ってリリース処理を行う必要があります。
このメソッドは生成されたインスタンスを破棄しつつ、参照カウンタをデクリメントします。

またAddressables.InstantiateAsync()で生成した場合、生成されたインスタンスに関連するリソースの寿命は所属するシーンに依存します。
つまり、Addressables.InstantiateAsync()で生成されたGameObjectが所属するシーンが破棄されると、
それに連動してAddressables.ReleaseInstance()と同等のリリース処理が自動的に行われます。

ちなみにこのAddressables.ReleaseInstance()の引数にはAsyncOperationHandleかインスタンス自身のどちらかを渡しますが、
Addressables.InstantiateAsync()trackHandleという引数を持っていて、
これにfalseを渡すとAsyncOperationHandleでしかリリースができなくなります。
(処理負荷が下がるようですが制御しづらくなるので微妙な感じです)

どちらを使えばいいのか?

それではGameObjectをインスタンス化する際には上記二つの方法のどちらを使用すればいいのでしょうか。

この答えとしては、基本的にはインスタンス化のためのAPIであるAddressables.InstantiateAsync()を使うのがよさそうです。
ただし上記のリリース方法の特性をよく理解して使う必要があります。

また、Addressables.InstantiateAsync()には若干のオーバーヘッドが発生するため、
1フレームで同じGameObjectを数百個以上生成するようなケースでは、
一回Addressables.LoadAssetAsync()を呼んでからGameObject.Instantiate()を数百回呼んだ方が効率が良いようです。

シーンのアンロードタイミング

Addressableを使う場合、シーンはAddressables.LoadSceneAsync()を使ってロードします。

Addressables.LoadSceneAsync("ExampleScene");

こうしてロードしたシーンはAddressables.UnloadScene()を使ってアンロードできます。

ただし、他のシーンをSingleモードで読み込んだ場合には読み込まれる前に存在していたシーンは自動的にアンロードされます。

その他明示的なリリースが必要なもの

Addressables.LoadResourceLocationsAsync()とAddressables.GetDownloadSizeAsync()は、
明示的なリリースが行われるまでデータを保持し続けます。

よって、不要になった時点でこれらのAsyncOperationHandleに対してAddressables.Release()する必要があります。

自動的に解放されるもの

Addressables.DownloadDependenciesAsync()とかAddressables.UnloadScene()といったメソッドは
戻り値としてAsyncOperationHandleを返すものの、AsyncOperationHandle.Resultとして受け取るものがありません。

このようなAsyncOperationHandleを返すメソッドにはautoReleaseHandleというが用意されています。
これをtrueにすると処理が終わった後にHandleが自動的にリリースされます。

参照カウントを見る

さてこのようにしてインクリメント/デクリメントされる参照カウントを可視化するためにはEvent Viewerを使用します。
これについては以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

関連

light11.hatenadiary.com

light11.hatenadiary.com

light11.hatenadiary.com

参考

docs.unity3d.com