【Unity】Test Runnerでパフォーマンステストを行うPerformance Testing Extensionの使い方まとめ

UnityのTest Runnerでパフォーマンステストを行うPerformance Testing Extensionの使い方についてまとめました。

Unity2020.2.7f1
Performance Testing Extension 2.8.0-preview

はじめに

Performance Testing Extension for Unity Test Frameworkは、
UnityのTest Runnerでパフォーマンステストを行うための拡張機能です。
処理時間やメモリ量などを計測し、結果を視覚的に表示することができます。

docs.unity3d.com

使用にあたってはTest Runnerの基礎知識があることが前提となります。
Test Runnerについては以下の記事などにもまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

セットアップ

それではPerformance Testing Extensionsをセットアップしていきます。
まずPackages/manifest.jsonを開いてdependenciesブロックを以下のように追記してインストールを行います。

{
    "dependencies": {
        "com.unity.test-framework.performance": "2.8.0-preview"
    }
}

末尾のバージョンは適宜最新のものなどに書き換えてください。

インストールが完了したらテスト用のAsmdefのAssembly Definition ReferencesにUnity.PerformanceTestingを追加します。

f:id:halya_11:20210507120024p:plain
参照を追加

また、パフォーマンステストをする際にはVSyncを無効にしておいた方がよさそうです。

メソッドのパフォーマンスを計測する

さてそれでは早速パフォーマンスの計測を行います。
まずはメソッドのパフォーマンスを計測してみます。

using NUnit.Framework;
using Unity.PerformanceTesting;
using UnityEngine;

namespace PerformanceTesting.Tests
{
    public class ExampleTest
    {
        // Performanceアトリビュートを付ける
        [Test]
        [Performance]
        public void MethodPerformanceTest()
        {
            var text = "Test";

            // メソッドのパフォーマンスを計測するにはMeasure.Method()を使う
            Measure.Method(() =>
                {
                    // Debug.Logの処理時間を計測
                    Debug.Log(text);
                })
                .WarmupCount(5) // 記録する前に何回か処理を走らせる(安定性を向上させるため)
                .IterationsPerMeasurement(100) // 計測一回辺りに走らせる処理の回数
                .MeasurementCount(20) // 計測数
                .Run();
        }
    }
}

上記のサンプルではDebug.Logのパフォーマンスを計測してみました。
細かい説明はコメントを参照してください。

このテストを走らせることで計測が完了します。

計測結果を見る

次に前節で計測を行ったテストの結果を見てみます。
Window > Analysis > Performance Test ReportからTest Reportウィンドウを開きます。
実施したテストの一覧が表示からテストを選択すると、その結果が以下のように可視化されます。

f:id:halya_11:20210507122705p:plain
Test Report

試行回数ごとの処理時間や平均、標準偏差などを見ることができます。

ちなみにPerformanceBenchmarkReporterを使うとこの結果をHTML形式に変換することができます。

github.com

これについては別の記事にまとめる予定です。

プロファイラのマーカーを指定して特定の処理のパフォーマンスを計測する

次にプロファイラのマーカーを指定して特定の処理のパフォーマンスを計測します。
これは以下のようにフレーム単位で計測を行うMeasure.Framesを使います。

using System.Collections;
using NUnit.Framework;
using Unity.PerformanceTesting;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.TestTools;

namespace PerformanceTesting.Tests
{
    public class ExampleTest
    {
        private GameObject _parent;
        
        [SetUp]
        public void SetUp()
        {
            // 各テスト開始時にGameObjectを作っておく
            _parent = new GameObject("Root");
            for (var i = 0; i < 1000; ++i)
            {
                var example = new GameObject("Example" + i, typeof(Example));
                example.transform.SetParent(_parent.transform);
            }
        }

        [TearDown]
        public void TearDown()
        {
            // 各テスト終了後に、開始時に作ったGameObjectを破棄する
            Object.DestroyImmediate(_parent);
        }

        [UnityTest, Performance]
        public IEnumerator MeasureWithProfilerMarker()
        {
            const string profilerMarkers = "FindGameObject";

            // Measure.Framesでフレーム毎の処理時間を計測できる
            yield return Measure.Frames()
                .WarmupCount(3) // 計測を始めるまでのフレーム数
                .DontRecordFrametime() // 結果にフレーム自体の計測時間を残さなくていいならこれを付けておく
                .MeasurementCount(20) // 何フレーム計測するか
                .ProfilerMarkers(profilerMarkers) // 計測したいProfilerMarkerを指定
                .Run();
        }
        
        public class Example : MonoBehaviour
        {
            private static readonly ProfilerMarker Marker = new ProfilerMarker("FindGameObject");
            
            private void Update()
            {
                // FindGameObjectマーカーを付けてGameObject.Findする
                using (Marker.Auto())
                {
                    GameObject.Find("Test");
                }
            }
        }
    }
}

細かい説明はコメントに書いたので割愛します。

このテストを実行すると以下のような結果が得られます。

f:id:halya_11:20210507151111p:plain
計測結果

スコープを使って処理時間を計測する

以下のようにMeasure.Scopeをusingステートメントで囲むと、その部分の処理時間を1回の計測結果として扱えます。

using NUnit.Framework;
using Unity.PerformanceTesting;
using UnityEngine;

namespace PerformanceTesting.Tests
{
    public class ExampleTest
    {
        [Test, Performance]
        public void ScopeTest()
        {
            // スコープの名前と単位を決められる
            var sampleGroup = new SampleGroup("Example", SampleUnit.Microsecond);
            
            // Debug.Logの時間を30回計測する
            for (var i = 0; i < 30; i++)
            {
                using (Measure.Scope(sampleGroup))
                {
                    Debug.Log("Test");
                }
            }

        }
    }
}

時間以外のものを計測する

以下のようにMeasure.Customを使うと時間以外のものも自由に計測結果として記録することができます。
以下の例ではメモリ量を記録しています。

using NUnit.Framework;
using Unity.PerformanceTesting;
using UnityEngine.Profiling;

namespace PerformanceTesting.Tests
{
    public class ExampleTest
    {
        [Test, Performance]
        public void CustomTest()
        {
            var allocated = new SampleGroup("TotalAllocatedMemory", SampleUnit.Megabyte);
            var reserved = new SampleGroup("TotalReservedMemory", SampleUnit.Megabyte);

            Measure.Custom(allocated, Profiler.GetTotalAllocatedMemoryLong() / 1048576f);
            Measure.Custom(reserved, Profiler.GetTotalReservedMemoryLong() / 1048576f);
        }
    }
}

関連

light11.hatenadiary.com

参考

docs.unity3d.com

blogs.unity3d.com

github.com