0. この記事は?
もう、タイトル通り。
列挙体は、デフォでは(数字はできても)文字列にはキャストできないので、ではどんな実装ならばパフォーマンスがいいのか、調べて考察するというもの。
1. 列挙体をそのまま ToString()
しちゃう
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. 連想配列を使うパターン
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 |
速度以外のパフォーマンス面でいうと、Dictionary
は static readonly
、いわゆる実行時定数になっているため、そのオーバーヘッドは考えておかないといけない。
あと、コーディング体験の話をいえば、連想配列定義側と列挙体と個別に定義しないといけないので追加が結構手間ではある。
登録する文字列の自由度は、特に問題ないかな。
2-α IEquatable
が登録されたKeyで連想配列
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
の本領発揮ということで、めちゃ速い。
いちいちキャストしているため、コーディング体験はもう一つ下がる感じ。
なお、byte
や short
, long
などほかの型でも試してみたが、なぜかダントツで int
型が速かった。
2-β いっそ連想配列やめてリストにしたら...?
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
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>
に乾杯...(でも、たぶん使わん)。