LoginSignup
12

More than 5 years have passed since last update.

Unity/C#でDictionaryのキーに列挙型を使用した時の速度比較

Last updated at Posted at 2016-05-02

Unite2016において、DirectionaryのKeyにenumを使用した場合にボックス化(objectクラスとしての管理)が発生し
その対策としてIEqualityComparerが挙げられていた
http://qiita.com/baba_s/items/5ee3079ef2797abdcf99

ややこしいことしないで列挙体なんてint型にキャストすればいいじゃないか
ということで検証

【検証環境】

・Unity5.3.4f1(Personal Edition)
・Nexus7(Android 5.0.2)
・10万回ループした際にかかった時間を比較

【比較項目】

①DirectionaryのKey値にenumを使用
②DirectionaryのKey値にenumを使用しIEqualityComparerを指定(※GetHashCodeをただのラッパー)
③DirectionaryのKey値にenumを使用しIEqualityComparerを指定(※GetHashCodeをオーバーライド)
④DirectionaryのKey値はenumをintキャストしたもの
⑤DirectionaryのKey値はenumをintキャストしたものを使用しIEqualityComparerを指定(※GetHashCodeをオーバーライド)

【テストコード】

using UnityEngine;
using System.Collections.Generic;

public class Test : MonoBehaviour {

    private enum JobType : int {
        ONE,
        TWO,
        THREE,
    }

    private class JobTypeCompare1 : IEqualityComparer<JobType> {
        public bool Equals(JobType x, JobType y) {
            return x == y;
        }
        public int GetHashCode(JobType obj) {
            return obj.GetHashCode();
        }
    }

    private class JobTypeCompare2 : IEqualityComparer<JobType> {
        public bool Equals(JobType x, JobType y) {
            return x == y;
        }
        public int GetHashCode(JobType obj) {
            return (int)obj;
        }
    }

    private class JobTypeCompare3 : IEqualityComparer<int> {
        public bool Equals(int x, int y) {
            return x == y;
        }

        public int GetHashCode(int obj) {
            return obj;
        }
    }

    void Start() {
        const int count = 100000;

        // ①DirectionaryのKey値にenumを使用
        {
            var dict = new Dictionary<JobType, int>(count);
            var startTime = Time.realtimeSinceStartup;
            for (var i = 0; i < count; ++i)
                dict.Add((JobType)i, i);
            var result = Time.realtimeSinceStartup - startTime;
            Debug.Log("Enum Key : " + result);

            startTime = Time.realtimeSinceStartup;
            int val = 0;
            for (var i = 0; i < count; ++i)
                dict.TryGetValue(JobType.TWO, out val);
            result = Time.realtimeSinceStartup - startTime;
            Debug.Log("Enum Key Get: " + result);
        }

        // ②DirectionaryのKey値にenumを使用しIEqualityComparerを指定(※GetHashCodeをいじらない)
        {
            var dict = new Dictionary<JobType, int>(count, new JobTypeCompare1());
            var startTime = Time.realtimeSinceStartup;
            for (var i = 0; i < count; ++i)
                dict.Add((JobType)i, i);
            var result = Time.realtimeSinceStartup - startTime;
            Debug.Log("Enum Compare1 Key : " + result);

            startTime = Time.realtimeSinceStartup;
            int val = 0;
            for (var i = 0; i < count; ++i)
                dict.TryGetValue(JobType.TWO, out val);
            result = Time.realtimeSinceStartup - startTime;
            Debug.Log("Enum Compare1 Key Get: " + result);
        }

        // ③DirectionaryのKey値にenumを使用しIEqualityComparerを指定(※GetHashCodeをオーバーライド)
        {
            var dict = new Dictionary<JobType, int>(count, new JobTypeCompare2());
            var startTime = Time.realtimeSinceStartup;
            for (var i = 0; i < count; ++i)
                dict.Add((JobType)i, i);
            var result = Time.realtimeSinceStartup - startTime;
            Debug.Log("Enum Compare2 Key : " + result);

            startTime = Time.realtimeSinceStartup;
            int val = 0;
            for (var i = 0; i < count; ++i)
                dict.TryGetValue(JobType.TWO, out val);
            result = Time.realtimeSinceStartup - startTime;
            Debug.Log("Enum Compare2 Key Get: " + result);
        }

        // ④DirectionaryのKey値はenumをintキャストしたもの
        {
            var dict = new Dictionary<int, int>(count);
            var startTime = Time.realtimeSinceStartup;
            for (var i = 0; i < count; ++i)
                dict.Add(i, i);
            var result = Time.realtimeSinceStartup - startTime;
            Debug.Log("int Key : " + result);

            int val = 0;
            startTime = Time.realtimeSinceStartup;
            for (var i = 0; i < count; ++i)
                dict.TryGetValue((int)JobType.TWO, out val);
            result = Time.realtimeSinceStartup - startTime;
            Debug.Log("int Key Get: " + result);
        }
        // ⑤DirectionaryのKey値はenumをintキャストしたものを使用しIEqualityComparerを指定(※GetHashCodeをオーバーライド)
        {
            var dict = new Dictionary<int, int>(count, new JobTypeCompare3());
            var startTime = Time.realtimeSinceStartup;
            for (var i = 0; i < count; ++i)
                dict.Add(i, i);
            var result = Time.realtimeSinceStartup - startTime;
            Debug.Log("Enum Compare3 Key : " + result);

            int val = 0;
            startTime = Time.realtimeSinceStartup;
            for (var i = 0; i < count; ++i)
                dict.TryGetValue((int)JobType.TWO, out val);
            result = Time.realtimeSinceStartup - startTime;
            Debug.Log("Enum Compare3 Key Get: " + result);
        }
    }
}

【結果】

① 素のenum
  (Add)0.09005737 / (Get)0.2161255
② IEqualityComparer使用のenum
  (Add)0.08615112 / (Get)0.1058655
③ IEqualityComparer使用のGetHashCodeオーバーライドのenum
  (Add)0.03231812 / (Get)0.03015137
④ intキャストのenum
  (Add)0.03274536 / (Get)0.03060913 ※Addについては今回enum⇒intキャストを行っていないので参考程度
⑤ intキャストのIEqualityComparer使用のenum
  (Add)0.03240967 / (Get)0.03030396 ※Addについては今回enum⇒intキャストを行っていないので参考程度

【総評】

③のIEqualityComparerの方が④のintキャストよりAddの速度が確実に早いが取得速度は誤差範囲
④と⑤は誤差レベルなので費用対効果的な面を考えるとやはり単純なintキャストでいい
しかし何故かEditor上だと④は③と⑤より確実に遅い、int型の内部でtry-catchでもやっているのだろうか?
確実な速度にこだわるのであれば③番のIEqualityComparerを作った方がいい
そこまでするよりDictionaryを固定配列にした方が20倍ぐらい速くなるが

【余談】

・List.Findは激遅いのでデータ数が少ないからといって採用してはいけない
・Collection全般に言えることだがキャパシティは指定しないと足りなくなった際にAddでメモリアロケーションが起きるので極力すべき

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
12