AsyncReadManagerMetrics クラスを使用した Unity のアセットロード計測

インターネットをご覧の皆さん こんばんは。
Unity のドキュメントで、アセットローディングメトリクスというページを見かけたので、試してみたことをメモがてら記事を書いてみます。

試した時点の環境は Unity2021.3.10f1 です。

# AsyncReadManagerMetrics
https://docs.unity3d.com/ja/current/Manual/AsyncReadManagerMetrics.html

> Unity エンジンは、AsyncReadManager を使用して、ランタイムにほとんどのファイルを読み込みます。

> AsyncReadManagerMetrics クラスを使用して、ランタイムのアセットのロードとファイル読み込みのパフォーマンスを監視します。このクラスは、AsyncReadManager によって管理されるすべてのファイル読み込み操作に関するデータを記録します。

と書かれている通り、AsyncReadManagerMetrics クラスを使用して情報収集します。
ただし、

> 可能な場合は、メトリックデータには読み込み操作に関するコンテキスト情報が含まれています。この情報には、読み込みを要求した AssetLoadingSubsystem、AssetName、AssetTypeID が含まれます。AsyncReadManager がこの情報にアクセスできない場合、これらのメトリクスフィールドの値は、それぞれ Other、空白、0 です。

とあるように、AssetName が取得できない場合があります。
FileName は取得できるのですが、実行環境によってファイル名からアセットを類推するのが難しかったりします。
例えば、Editor かつ AssetDatabase を使用するパターンだと、FileName には Artifact ファイル名が入っています。
`D:/AsyncReadManagerMetricsSample/Library/Artifacts/44/443c2d06875ad1ccf3009d49738f637a`

あまり Editor 実行時の計測をする機会は無いかもしれませんが、Artifact ファイル名から Asset ファイル名に変換する処理を入れてみたものが以下になります。

```cs:AssetLoadMetrics.cs
using System.Collections.Generic;
using System.IO;
using Unity.IO.LowLevel.Unsafe;

#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Experimental;
#endif

public static class AssetLoadMetrics
{
    private static readonly Dictionary<string, string> _assetByArtifact;

    static AssetLoadMetrics()
    {
#if UNITY_EDITOR
        // Artifact ファイル名を収集
        _assetByArtifact = new();

        var assets = AssetDatabase.GetAllAssetPaths();
        foreach (var asset in assets)
        {
            // Asset の GUID から Artifact ファイル名を収集
            var guidString = AssetDatabase.AssetPathToGUID(asset);
            var artifactKey = new ArtifactKey(new GUID(guidString));
            var artifactID = AssetDatabaseExperimental.LookupArtifact(artifactKey);
            AssetDatabaseExperimental.GetArtifactPaths(artifactID, out var paths);
            foreach (var path in paths)
            {
                var artifactFileName = Path.GetFileName(Path.GetFullPath(path)).ToLower();
                _assetByArtifact[artifactFileName] = asset;
            }
        }
#endif
    }

    public static void StartCollectingMetrics()
    {
#if ENABLE_PROFILER && UNITY_2020_2_OR_NEWER
        AsyncReadManagerMetrics.StartCollectingMetrics();
#endif
    }

    public static void EndCollectingMetrics(StreamWriter writer)
    {
#if ENABLE_PROFILER && UNITY_2020_2_OR_NEWER
        writer.WriteLine("AssetName,FileName,OffsetBytes,SizeBytes,AssetTypeId,CurrentBytesRead,BatchReadCount,IsBatchRead,State,ReadType,PriorityLevel,Subsystem,RequestTimeMicroseconds,TimeInQueueMicroseconds,TotalTimeMicroseconds");

        var metrics = AsyncReadManagerMetrics.GetMetrics(AsyncReadManagerMetrics.Flags.ClearOnRead);
        foreach (var metric in metrics)
        {
            var asset = metric.AssetName;
            // Asset 名が無い場合で Artifact ファイルなら逆引きを試みる
            if (string.IsNullOrWhiteSpace(asset) && metric.FileName.Contains("Artifacts"))
            {
                _assetByArtifact.TryGetValue(Path.GetFileName(metric.FileName).ToLower(), out asset);
            }

            writer.WriteLine($"{asset},{metric.FileName},{metric.OffsetBytes},{metric.SizeBytes},{metric.AssetTypeId},{metric.CurrentBytesRead},{metric.BatchReadCount},{metric.IsBatchRead},{metric.State},{metric.ReadType},{metric.PriorityLevel},{metric.Subsystem},{metric.RequestTimeMicroseconds},{metric.TimeInQueueMicroseconds},{metric.TotalTimeMicroseconds}");
        }

        AsyncReadManagerMetrics.StopCollectingMetrics();
# endif
    }
}
```

出力結果はこんな感じのデータ列になります。
`Assets/Prefabs/PrefabCube.prefab,D:/AsyncReadManagerMetricsSample/Library/Artifacts/44/443c2d06875ad1ccf3009d49738f637a,0,7168,0,7168,0,False,Completed,Sync,PriorityHigh,Other,21600468747.2,60.5999984741211,111.099998474121`


※ ちなみに、Artifact まわりの処理は下記 Blog の「ディスク上のインポート結果を見つける方法」を参考にしました。

https://blog.unity.com/ja/technology/tips-for-working-more-effectively-with-the-asset-database

[追記]
プロファイラでも見れたようです。気付かなかった。

https://docs.unity3d.com/ja/current/Manual/profiler-asset-loading-module.html

https://docs.unity3d.com/ja/current/Manual/profiler-file-access-module.html

# おわりに
実機での実行や Addressable 等を使う場合はもう少し工夫が必要かもしれず、パフォーマンスチューニングにそのまま活用できるかはわかりませんが、普段ファイルアクセスの状況を気にする機会が少なかったので、何かしらの気付きが得られそうだなという雰囲気を感じました。

AsyncReadManagerMetrics にはサマリーを取得する機能もあるようなので、場合によってはそちらを使うほうが良いかもしれません。
※ UnityEditor 上で実機の情報を取得するパッケージを公開されている方もいらっしゃるようです。

https://github.com/katsumasa/AsyncReadManagerMetricsRemote

以上 おつきあいいただきありがとうございました。