コガネブログ

平日更新を目標に Unity や C#、Visual Studio、ReSharper などのゲーム開発アレコレを書いていきます

【Unity】Dictionary のキーに構造体を使う時のパフォーマンスを測定

ソースコード

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using NUnit.Framework;
using Unity.PerformanceTesting;

// Dictionary のキーに構造体を使用する時のパフォーマンスを測定するクラス
public sealed class DictionaryStructKeyTest
{
    // 通常の構造体
    [SuppressMessage( "ReSharper", "NotAccessedField.Local" )]
    private readonly struct KeyStruct1
    {
        private readonly int m_primaryId;
        private readonly int m_secondaryId;

        public KeyStruct1( int primaryId, int secondaryId )
        {
            m_primaryId   = primaryId;
            m_secondaryId = secondaryId;
        }
    }

    // IEquatable インターフェイスを実装した構造体
    private readonly struct KeyStruct2 : IEquatable<KeyStruct2>
    {
        private readonly int m_primaryId;
        private readonly int m_secondaryId;

        public KeyStruct2( int primaryId, int secondaryId )
        {
            m_primaryId   = primaryId;
            m_secondaryId = secondaryId;
        }

        public bool Equals( KeyStruct2 other )
        {
            return m_primaryId == other.m_primaryId &&
                   m_secondaryId == other.m_secondaryId;
        }
    }

    // IEquatable インターフェイスを実装して
    // Equals メソッドと GetHashCode メソッドをオーバーライドした構造体
    private readonly struct KeyStruct3 : IEquatable<KeyStruct3>
    {
        private readonly int m_primaryId;
        private readonly int m_secondaryId;

        public KeyStruct3( int primaryId, int secondaryId )
        {
            m_primaryId   = primaryId;
            m_secondaryId = secondaryId;
        }

        public bool Equals( KeyStruct3 other )
        {
            return m_primaryId == other.m_primaryId &&
                   m_secondaryId == other.m_secondaryId;
        }

        public override bool Equals( object obj )
        {
            return obj is KeyStruct3 other && Equals( other );
        }

        public override int GetHashCode()
        {
            return HashCode.Combine( m_primaryId, m_secondaryId );
        }
    }

    // Dictionary のキーに通常の構造体を使用する時のパフォーマンスを測定
    [Test]
    [Performance]
    public void StructTest1()
    {
        var dictionary = new Dictionary<KeyStruct1, string>();
        var key        = new KeyStruct1( 1, 2 );

        dictionary[ key ] = "";

        Run( () => dictionary.GetValueOrDefault( key ) );
    }

    // Dictionary のキーに IEquatable インターフェイスを実装した構造体を使用する時のパフォーマンスを測定
    [Test]
    [Performance]
    public void StructTest2()
    {
        var dictionary = new Dictionary<KeyStruct2, string>();
        var key        = new KeyStruct2( 1, 2 );

        dictionary[ key ] = "";

        Run( () => dictionary.GetValueOrDefault( key ) );
    }

    // Dictionary のキーにIEquatable インターフェイスを実装して
    // Equals メソッドと GetHashCode メソッドをオーバーライドした構造体を使用する時のパフォーマンスを測定
    [Test]
    [Performance]
    public void StructTest3()
    {
        var dictionary = new Dictionary<KeyStruct3, string>();
        var key        = new KeyStruct3( 1, 2 );

        dictionary[ key ] = "";

        Run( () => dictionary.GetValueOrDefault( key ) );
    }

    // 指定された処理を実行してパフォーマンスを測定
    private static void Run( Action action )
    {
        const int warmupCount              = 10;     // 測定を開始する前に実行する回数
        const int measurementCount         = 100;    // 測定数
        const int iterationsPerMeasurement = 100000; // 測定ごとのメソッドの実行回数

        Measure
            .Method( action )
            .WarmupCount( warmupCount )
            .MeasurementCount( measurementCount )
            .IterationsPerMeasurement( iterationsPerMeasurement )
            .GC()
            .Run()
            ;
    }
}

測定結果

通常の構造体

f:id:baba_s:20220217125143p:plain

IEquatable インターフェイスを実装した構造体

f:id:baba_s:20220217125145p:plain

IEquatable インターフェイスを実装して Equals メソッドと GetHashCode メソッドをオーバーライドした構造体

f:id:baba_s:20220217125148p:plain

検証環境

  • Unity 2021.2.11f1