LoginSignup
9
2

列挙体と文字列を紐づける実装のパフォーマンス調査

Last updated at Posted at 2024-02-03

0. この記事は?

もう、タイトル通り。
列挙体は、デフォでは(数字はできても)文字列にはキャストできないので、ではどんな実装ならばパフォーマンスがいいのか、調べて考察するというもの。

環境

  • Unity2022.3.18f1
  • .NET Standard2.1
  • Mono

1. 列挙体をそのまま ToString() しちゃう

Test.cs
using System.Diagnostics;
using UnityEngine;

public sealed class Test : MonoBehaviour
{
    private void Start()
    {
        var sw = new Stopwatch();

        sw.Start();
        
        string hoge = Fruits.Apple.ToString();
        
        sw.Stop();
        
        print($"処理時間:{sw.Elapsed}");
    }
}

enum Fruits
{
    Apple,
    Banana,
    Peach
}
回数 計測時間
1回目 00:00:00.0000389
2回目 00:00:00.0000392
3回目 00:00:00.0000399
平均時間 00:00:00.0000393

一番、アリエナイ。
登録する文字列の自由度が皆無。
例えば "Main Window" みたいな文字列(つまり半角スペース入っていたり)が登録できない。
速度パフォーマンスが一番よくても...たぶん使わないなあ。

2. 連想配列を使うパターン

Test.cs
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;

public sealed class Test : MonoBehaviour
{
    static readonly Dictionary<Fruits, string> dic = new Dictionary<Fruits, string>
    {
        { Fruits.Apple, "Apple" },
        { Fruits.Banana, "Banana" },
        { Fruits.Peach, "Peach" }
    };

    private void Start()
    {
        var sw = new Stopwatch();

        sw.Start();

        string hoge = dic[Fruits.Apple];

        sw.Stop();

        print($"処理時間:{sw.Elapsed}");
    }
}

enum Fruits
{
    Apple,
    Banana,
    Peach
}
回数 計測時間
1回目 00:00:00.0003144
2回目 00:00:00.0003083
3回目 00:00:00.0003016
平均時間 00:00:00.0003081

速度以外のパフォーマンス面でいうと、Dictionarystatic readonly、いわゆる実行時定数になっているため、そのオーバーヘッドは考えておかないといけない。

あと、コーディング体験の話をいえば、連想配列定義側と列挙体と個別に定義しないといけないので追加が結構手間ではある。
登録する文字列の自由度は、特に問題ないかな。

2-α IEquatable が登録されたKeyで連想配列

Test.cs
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;

public sealed class Test : MonoBehaviour
{
    static readonly Dictionary<int, string> dic = new Dictionary<int, string>
    {
        { (int)Fruits.Apple, "Apple" },
        { (int)Fruits.Banana, "Banana" },
        { (int)Fruits.Peach, "Peach" }
    };

    private void Start()
    {
        var sw = new Stopwatch();

        sw.Start();

        string hoge = dic[(int)Fruits.Apple];

        sw.Stop();

        print($"処理時間:{sw.Elapsed}");
    }
}

enum Fruits : int
{
    Apple = 0,
    Banana = 1,
    Peach = 2
}
回数 計測時間
1回目 00:00:00.0000705
2回目 00:00:00.0000713
3回目 00:00:00.0000726
平均時間 00:00:00.0000715

直前の項目の亜種。

このようにパフォーマンス上の理由があるため、DictionaryやListといった多くのクラスの等価性判定を伴う処理では、IEquatableを実装している型についてはObject.Equals(Object)ではなくIEquatable.Equals(T)を呼ぶように最適化されています。

こちらに従って、IEquatable<T> の実装がある int 型にいちいちキャストする実装でテストしてみた。

いちおう Dictionary の本領発揮ということで、めちゃ速い。
いちいちキャストしているため、コーディング体験はもう一つ下がる感じ。

なお、byteshort , long などほかの型でも試してみたが、なぜかダントツで int 型が速かった。

2-β いっそ連想配列やめてリストにしたら...?

Test.cs
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;

public sealed class Test : MonoBehaviour
{
    static readonly List<string> list = new List<string>
    {
        "Apple",
        "Banana",
        "Peach"
    };

    private void Start()
    {
        var sw = new Stopwatch();

        sw.Start();

        string hoge = list[(int)Fruits.Apple];

        sw.Stop();

        print($"処理時間:{sw.Elapsed}");
    }
}

enum Fruits : int
{
    Apple = 0,
    Banana = 1,
    Peach = 2
}
回数 計測時間
1回目 00:00:00.0000329
2回目 00:00:00.0000343
3回目 00:00:00.0000345
平均時間 00:00:00.0000339

列挙体を int 型で扱うなら、もう連想配列いらなくないか、と思って試したら...なんだこの速度...!
ToString() メソッドを使うときに匹敵...なんなら勝っている!
最速やんけ...。
まあでも、拡張性という意味では、どこかでヒューマンミスしてずれそうな構図しているので、やるとしても自動生成コードとかでやる感じじゃないすかね。

3. FastEnum

Test.cs
using System.Diagnostics;
using System.Runtime.Serialization;
using FastEnumUtility;
using UnityEngine;

public sealed class Test : MonoBehaviour
{
    private void Start()
    {
        var sw = new Stopwatch();

        sw.Start();

        string hoge = Fruits.Apple.GetEnumMemberValue();

        sw.Stop();

        print($"処理時間:{sw.Elapsed}");
    }
}

enum Fruits
{
    [EnumMember(Value = "Apple")]
    Apple,
    [EnumMember(Value = "Banana")]
    Banana,
    [EnumMember(Value = "Peach")]
    Peach
}
回数 計測時間
1回目 00:00:00.0061827
2回目 00:00:00.0061641
3回目 00:00:00.0063316
平均時間 00:00:00.0062261

思ったよりもパフォーマンスいい。
一番のメリットは、EnumMember 属性を使うことによる可読性・保守性の高さ。
本来、EnumMember 属性を使ったこういう実装はもっとパフォーマンスが悪いものだが、まだ許容できるパフォーマンスに持ってこれている感じ。

4. 感想

ToString() に勝る List<T> に乾杯...(でも、たぶん使わん)。

9
2
8

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
9
2