実はAssetBundleをロードする際の挙動として、内部的には2種類の挙動があり、それぞれに特性やパフォーマンスの違いがあります。
そんな細かすぎて伝わらないAssetBundleのお話です。
元々DownloadHandlerAssetBundleの挙動を調べていてすこし気になった挙動があり、
調べていくと以下のことが分かりました。
プロファイラのこの部分を見ると、それぞれどちらの領域がどれだけメモリを使っているかが分かります。
参考: 【Unite 2017 Tokyo】最適化をする前に覚えておきたい技術
ディスク上からロードする場合AssetBundleをディスク上からロードする場合、かつAssetBundleが 無圧縮 か LZ4圧縮 の場合は、以下のような仕組みで動きます。
そしてAssetBundleがディスク上からロードされるシチュエーションは以下の2つあります。
リファレンスに書いてある通り、必要な分だけしかロードしないため、パフォーマンス的には最も良いロード方法になります。
ちなみにLZMA圧縮の場合は試していませんが、多分アンマネージドメモリにまるごとロードされる下のパターンと同じ挙動になりそうです。
この挙動についてはあまり目立って言及されていませんが、公式マニュアルをよく読むとしれっとそれっぽい記述があります。
アセットバンドルを使いこなす - Unity マニュアル
AssetBundle.LoadFromFile
この API は、非圧縮バンドルをローカルストレージから読み込む際に使用すると非常に効率的です。 LoadFromFile は、バンドルが非圧縮の場合やチャンクベースの圧縮方式(LZ4)で圧縮されている場合に、
ディスクから直接バンドルを読み込みます
。完全圧縮(LZMA)バンドルをこのメソッドで読み込む場合は、バンドルが解凍されてからメモリに読み込まれます。
UnityWebRequest
UnityWebRequest には、アセットバンドル専用の API コールがあります。この使用を開始するには、 UnityWebRequest.GetAssetBundle を使用してウェブ リクエストを作成する必要があります。リクエストを返した後に、そのリクエスト オブジェクトを DownloadHandlerAssetBundle.GetContent(UnityWebRequest) 内に渡します。この GetContent コールがアセットバンドル オブジェクトを返します。
また、
バンドルの読み込み後に DownloadHandlerAssetBundle クラスの assetBundle プロパティを使用することで、 AssetBundle.LoadFromFile の効率性をもってアセットバンドルを読み込むことができます
。
LoadAsset時にUnable to open archive fileというエラーが出ます。但し、既にアセットがロード済みだった場合は問題ありません。
(これを検証する際、Finderやエクスプローラー上で操作すると、UnityEditorに戻った時に諸々の参照がクリアされて正しく再現できないので、C#上でファイル操作を行う必要がある)
以下のようなコードで再現できます。
using UnityEngine;
using System.IO;
public class AssetBundleLoadTest : Monobehaviour
AssetBundle assetBundle;
void Start()
// AssetBundleの配置場所
var path = Application.streamingAssetsPath + "/test";
// ヘッダ情報だけメモリ上に読まれる
this.assetBundle = AssetBundle.LoadFromFile(path);
// 適当なボタンに登録する
public void LoadAsset()
// AssetBundleからアセットのロードを試みる
var asset = this.assetBundle.LoadAsset<TextAsset>("Assets/test.bytes");
Debug.Log(asset.bytes.Length);
// 適当なボタンに登録する
public void MoveAsset()
// AssetBundleのファイル名を変える(削除するのと同じ効果が得られるはず)
var path = Application.streamingAssetsPath + "/test";
File.Move(path, path + "2");
先にLoadAssetを呼び出した場合、MoveAssetを呼び出した後に再度LoadAssetを呼び出しても正常にDebug.Logが出力されます。
しかし、先にMoveAssetを呼び出した場合、LoadAsset時にエラーが発生します。
メモリ上からロードする場合
上記以外の場合は、AssetBundleのデータがまるごとアンマネージドメモリ上にロードされます。LoadAsset時には、ヘッダ情報を元に既にロード済みのアセットの参照の位置が返されるようなイメージです。それ以外は上記と同じです。
また、AssetBundle.LoadFromMemoryを使う場合、C#側(マネージドメモリ)で確保したbyte[]を渡す形になり、内部的にはそれがアンマネージドメモリに(LZ4の場合は)そのままコピーされる形になるため、一時的にメモリのスパイクが発生すると考えられます。
(DownloadHandlerAssetBundleを使用する場合はUnityネイティブ内で完結するのでその心配は無いはず)
また、AssetBundle.LoadFromStreamも同じ挙動だと考えられますが、こちらはストリームで決められたバッファしか確保しないため、LoadFromMemoryよりはメモリ効率は良さそうです。
DownloadHandlerAssetBundleの怪しい挙動
AssetBundleがディスク上にキャッシュされない場合(DL時にバージョン指定しない場合)はまるごとメモリ上にAssetBundleが乗るのですが、この時、DL完了時には既にメモリ上にAssetBundleのデータが乗っているものの、AssetBundleのヘッダ情報が認識されるのはDownloadHandlerAssetBundle.assetBundleにアクセスしたタイミングになります。
実際どういう事が起こるかというと、以下のような事が起こります。
// 仮に100MBのAssetBundleをロードするとする
var url = Application.streamingAssetsPath + "/test";
// バージョン指定しない=キャッシュされない=即メモリ上にまるごとロードされる
var handler = new DownloadHandlerAssetBundle(url, 0);
var request = new UnityWebRequest(
uri,
UnityWebRequest.kHttpVerbGET,
handler,
yield return request.SendWebRequest();
// このタイミングで100MBがアンマネージドメモリに乗っかる
// 通常これで全てのロード済みのAssetBundleがアンロードできるはずだが、ヘッダ情報がまだ認識されていないため、上でロードしたAssetBundleはアンロードされない!
AssetBundle.UnloadAllAssetBundles(true);
// なんでもいいのでassetBundleプロパティにアクセスするとこのタイミングでヘッダ情報が認識される
var ab = handler.assetBundle;
// ここでこれを呼ぶとアンロードできる
AssetBundle.UnloadAllAssetBundles(true);
DL時に読み込まれたAssetBundleを解放するには、ヘッダ情報を認識させてからUnloadを行うしか手段が無く、仮にassetBundleプロパティにアクセスしなかった場合、UnityWebRequestやDownloadHandlerを明示的にDisposeしようがGCを発生させようがResouces.UnloadUnusedAssetsを呼ぼうがどう足掻いても解放されません。
またヘッダ情報が認識されていない場合、同一AssetBundleもロードできてしまうため、上記コードのSendWebRequestまでを無限ループさせると、無限に解放できないメモリ確保が発生します。
まあキャッシュもしないのにDLだけしてassetBundleにアクセスしないシチュエーションは無いと思うのでまず実際に問題になる事はないと思いますが、微妙にバグっぽい挙動な気がしました。流石にDispose時に解放されるべきなのでは…?
AssetBundleはディスクから読むのが一番効率が良い
キャッシュを使うかLoadFromFileを使うと、無圧縮かLZ4の場合はいい感じに必要になったタイミングで必要な分だけディスクから読んでくれる
逆に言えば初回LoadAsset時にディスクI/Oが発生するため、既にメモリ上に読まれている場合よりかは遅くなることが考えられる。LoadAssetを最速にしたいなら、必要なアセットだけ事前にメモリに乗せておくなどの工夫が必要かもしれない
DownloadHandlerAssetBundleには若干怪しい挙動があるが、まああまり気にしなくても良い
アセットバンドルを使いこなす - Unity マニュアル
AssetBundle usage patterns - Unity
【Unite 2017 Tokyo】最適化をする前に覚えておきたい技術
【Unite 2016 Tokyo】学校では教えてくれないアセットバンドルのしくみ
最近Unity公式のマニュアルがかなり内部仕様にまで迫った上級者向けの記事が増えてきていて大変良いですね。