4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Unity] Addressables使用時のロード時間やメモリ消費量

Posted at

よく分からないまま使っていた部分があったので、PC/コンソール向けに挙動を検証してみました。

環境

  • Unity 6.2
  • Addressable 2.7.3
  • Windows DevelopmentBuild

検証

まず、Scriptを用いて8Kテクスチャを3枚とMeshで約1GBのオブジェクトを生成し、それを6つ作成しました。
image.png

また、以下のようなコードを用意し、任意のオブジェクトをロード/アンロードできる環境を作成しています。

using UnityEngine;
using UnityEngine.AddressableAssets;

public class ObjectLoader : MonoBehaviour
{
    const string KeyFormat = "Assets/GeneratedMeshes/GeneratedObject_{0:000}/GeneratedObject_{0:000}_Prefab.prefab";
    const int KeyCount = 6;
    
    private readonly Instance[] _instances = new Instance[KeyCount];
    
    private void Awake()
    {
        var cam = Camera.main;
        cam.transform.position = new Vector3(0, 10, 0);
        cam.transform.rotation = Quaternion.Euler(90, 0, 0);
        
        for (var i = 0; i < KeyCount; i++)
        {
            _instances[i] = new Instance(i);
        }
    }

    private void OnGUI()
    {
        for (var i = 0; i < KeyCount; i++)
        {
            GUILayout.BeginHorizontal();
            if (GUILayout.Button($"Load {i}"))
            {
                _instances[i].Load();
            }
            if (GUILayout.Button($"Unload {i}"))
            {
                _instances[i].UnLoad();
            }
            GUILayout.EndHorizontal();
        }
    }
    
    private class Instance
    {
        private readonly string _key;
        
        private bool _isLoading = false;
        private bool _isLoaded = false;
        private GameObject _gameObject;
        private GameObject _instanceObject;
        
        public bool IsLoaded => _isLoaded;
        
        public Instance(int index)
        {
            _key = string.Format(KeyFormat, index);
        }
        
        public void Load()
        {
            if (_isLoaded || _isLoading) return;
            _isLoading = true;
            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            
            Addressables.LoadAssetAsync<GameObject>(_key).Completed += (obj) =>
            {
                _gameObject = obj.Result;
                _isLoading = false;
                _isLoaded = true;
                _instanceObject = Instantiate(_gameObject);
                Debug.Log($"Load {_key} completed in {sw.ElapsedMilliseconds}ms");
            };
        }
        
        public void UnLoad()
        {
            if (!_isLoaded) return;
            Addressables.Release(_gameObject);
            _gameObject = null;
            _isLoaded = false;
            
            Destroy(_instanceObject);
            _instanceObject = null;
        }
    }
}

順番にロードする場合

全アセットを1つのAddressableにまとめ、デフォルト設定のまま順番にロードしてみました。
image.png
image.png
上からポチポチロードしていくと

Load GeneratedObject_000_Prefab.prefab completed in 10514ms
Load GeneratedObject_001_Prefab.prefab completed in 328ms
Load GeneratedObject_002_Prefab.prefab completed in 256ms
Load GeneratedObject_003_Prefab.prefab completed in 283ms
Load GeneratedObject_004_Prefab.prefab completed in 322ms
Load GeneratedObject_005_Prefab.prefab completed in 561ms
  • 初回ロード(000)が極端に遅い
  • 以降は比較的高速
    全部Unloadしたうえでもう一度やってもこれと同じような結果が出ます

メモリの挙動

LZ4圧縮では「ブロック単位で読み込み」が行われるため、初回に全Bundleを展開しているわけではありません。
メモリプロファイラで確認すると、ロード数に比例して使用量が増加しており、確かに逐次読み込みが行われています。
image.png

初回だけ遅い理由

となると何か一つ目が不思議ですね。プロファイラで見てみましょう
image.png
Profilerを確認すると、AssetBundleLoadFromAsyncOperation.IsCrc32Validが初回のみ大きく時間を消費していました。

全Unload後に再ロードしても再びスパイクが発生するため、適切にリソース管理しているほど頻繁にCRCチェックが走る ことになります。

CRCチェックを無効化

CRCチェックをGroup設定の方を変えてDisableにしてみましょう。
image.png

Load GeneratedObject_000_Prefab.prefab completed in 582ms
Load GeneratedObject_001_Prefab.prefab completed in 305ms
Load GeneratedObject_002_Prefab.prefab completed in 361ms
Load GeneratedObject_003_Prefab.prefab completed in 278ms
Load GeneratedObject_004_Prefab.prefab completed in 399ms
Load GeneratedObject_005_Prefab.prefab completed in 650ms

初回のスパイクは完全に消えました。
Remote配信時は検証が必要ですが、SteamやSwitchなどのコンソール向けでLocal利用が前提ならCRCチェックは不要と言えそうです。
bundleのサイズに比例して時間が伸びます。

ランダムアクセスの場合

次に、非順序でロード/アンロードを試しました。

  1. 0~5全てロード
  2. 0以外アンロード
  3. 1~5をもう一度ロード
Load GeneratedObject_000_Prefab.prefab completed in 489ms
Load GeneratedObject_001_Prefab.prefab completed in 322ms
Load GeneratedObject_002_Prefab.prefab completed in 261ms
Load GeneratedObject_003_Prefab.prefab completed in 300ms
Load GeneratedObject_004_Prefab.prefab completed in 566ms
Load GeneratedObject_005_Prefab.prefab completed in 572ms

# ここで0以外アンロード

Load GeneratedObject_001_Prefab.prefab completed in 10ms
Load GeneratedObject_002_Prefab.prefab completed in 10ms
Load GeneratedObject_003_Prefab.prefab completed in 11ms
Load GeneratedObject_004_Prefab.prefab completed in 10ms
Load GeneratedObject_005_Prefab.prefab completed in 10ms

二回目が異常に早いです。
これは流石にインメモリに居そうなのでメモリプロファイラ見てみましょう
image.png
0以外アンロードしたケースと全ロードしたケースでメモリ使用量があまり変わらないのでメモリ上に残ってます。
同一Bundle内のものを一気に読み込むことは無いが、何もしないと一つずつは破棄してくれずに、Bundle内のすべてのものがアンロードされたタイミングでメモリ上から全開放してます。

全部常駐してもOKな単位でBundleを区切るというのは一つの目安になりそうです。
一つが大きなPrefabなのでbundle自体を分割する設定のほうが良いでしょう。
image.png

Load GeneratedObject_000_Prefab.prefab completed in 613ms
Load GeneratedObject_001_Prefab.prefab completed in 588ms
Load GeneratedObject_002_Prefab.prefab completed in 511ms
Load GeneratedObject_003_Prefab.prefab completed in 356ms
Load GeneratedObject_004_Prefab.prefab completed in 728ms
Load GeneratedObject_005_Prefab.prefab completed in 522ms

# 以下↑と同じ結果になるので割愛

シーケンシャルアクセス出来ないのと、別Bundleでアクセス効率が下がっているので100msぐらい平均で読み込み時間は長くなってますが、一つずつメモリ上から高速にアンロードされてます。
image.png

UnCompress

以下にUnCompressが最速と書いてますがどうでしょうか
https://docs.unity3d.com/ja/Packages/com.unity.addressables@1.20/manual/GroupSettings.html#asset-bundle-compression

Load GeneratedObject_000_Prefab.prefab completed in 774ms
Load GeneratedObject_001_Prefab.prefab completed in 522ms
Load GeneratedObject_002_Prefab.prefab completed in 561ms
Load GeneratedObject_003_Prefab.prefab completed in 500ms
Load GeneratedObject_004_Prefab.prefab completed in 1228ms
Load GeneratedObject_005_Prefab.prefab completed in 1133ms

今回は特殊に大きなファイルを入れてるというのはあるが遅くなった。今回のケースだとDecompressではなくIOがネックらしい(かなり早いm.2 SSD上に置いてます)
ケースバイケースなのかもしれないが、遅くなる可能性があるくらいなら基本LZ4から変えることは無さそう

ProfilerPreloadManagerを見ると、ボトルネックが分かる。展開に時間かかってるケースがあればUnCompressも試してみたら良いかも
image.png

まとめ(実務メモ)

  • 初回スパイクの主因は CRC。ローカル運用前提なら CRC 無効化で大きく改善。リモート配信時は要要件検討
  • 同一バンドル内は “束” で生存する。個別に生殺与奪したいならバンドル分割を。逆に「常駐前提の塊」はまとめる
  • 圧縮は基本 LZ4(デフォルト)で開始。I/O vs CPU のどちらが詰まっているかを Profiler で見て、必要なら Uncompressed も試す
4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?