0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【tModLoader】ModContent.ItemType vs bool[] vs パターンマッチング vs HashSet<int>

Posted at

tModLoaderではアイテムの識別として、一般的にはitem.type == ModContent.ItemType<T>()を用いる。tModLoaderによるMod製作の基本だ。Mod製作を学んでいって使える技術を増やしても、最後までお世話になる(はず)。
学ぶのはTerrariaのMod製作だけではない。プログラミングとC#の基礎も学んでいく。
するとこう思うのだ。

...これって速いのかな?

毎フレーム呼ばれるメソッドでこの判定方法を使うたび疑問として浮かぶ。
最近では、「ModContent.ItemType<T>()ってなんかインスタンス取得してそこからTypeを取得してるから遅いんじゃね?」とか「計算量がO(1)だしHashSet<int>を使った方が速いんじゃね?」とか(勝手に)思ってHashSetを使ったりパターンマッチングを使ったりしている。
ただ、あくまで想像だけで言ってる。

折角だし、item.type == ModContent.ItemType<T>()よりパターンマッチングやHashSet<int>.Contains()の方が本当に速いのか計測してみることにした。

環境 及び 計測方法

計測には tModLoader v2022.9.47.87 (Terraria v1.4.3.6) を用いる。
計測用に新規でModは作成せず、自作のコンテンツMod(アイテムが約500個ある)内で計測する。
他に導入するModは必要最低限に留め、全ての計測で同じMod構成にする。
また、他のソフトウェアを終了した状態で行う。

実際に処理が行われる環境を可能な限り近づけたいので、ModPlayer.PreUpdate()内で計測する。
計測結果を安定させるために予めJourneyモードの機能でゲーム内時間を停止し、また計測中は一切の操作を行わない。

計測用に用いるサンプルは、「バニラアイテムのみ」「バニラとModアイテム半々」「Modアイテムのみ」「Modアイテムのみ (継承元が同じ)」の4つ。
私が毎フレームでアイテムの種類を判別する時は、主にホットバーのアイテムを対象とすることが多い。つまり、切り替えでアイテムが激しく変わるパターンとアイテム使用中でアイテムが全く変わらないパターンとして、このようなサンプルを用意した。

計測のコードは次の通り:

// ItemID.Setsからの取得みたいな感じ
internal static class TestID
{
    internal static class Sets
    {
        internal static void Initialize()
        {
            // ItemA, ItemB, ItemC, ItemD は ModItemを継承しているItemAlphabetを継承している
            int itemTypeA = ModContent.ItemType<ItemA>();
            int itemTypeB = ModContent.ItemType<ItemB>();
            int itemTypeC = ModContent.ItemType<ItemC>();
            int itemTypeD = ModContent.ItemType<ItemD>();
            TestArray = ItemID.Sets.Factory.CreateBoolSet(itemTypeA, itemTypeB, itemTypeC, itemTypeD);
            TestSet = new HashSet<int>()
            {
                itemTypeA, itemTypeB, itemTypeC, itemTypeD
            };
        }

        internal static bool[] TestArray;

        internal static HashSet<int> TestSet;
    }
}
internal class Tester : ModPlayer
{
    private const int SAMPLE_COUNT = 1000000; // 計測に用いるサンプル数
    private const int SAMPLE_TARGET_COUNT = SAMPLE_COUNT / 2;

    private const int TIME1 = 10 * 36; // 10秒後
    private const int TIME2 = 15 * 60; // 15秒後
    private const int TIME3 = 20 * 60; // 20秒後
    private const int TIME4 = 25 * 60; // 25秒後

    private int frameCounter;

    private List<int> itemAlphabetTypes; // ItemAlphabetを継承しているModアイテムの一覧

    public override void OnEnterWorld(Player player)
    {
        TestID.Sets.Initialize();
        itemAlphabetTypes = TestID.Sets.TestSet.ToList();
        // ワールドに入った直後では処理が混むので一定時間後に計測を行う
        frameCounter = 0;
    }

    public override void PreUpdate()
    {
        frameCounter++;
        if (frameCounter == TIME1)
        {
            Item[] sample = CreateSample(ItemID.None + 1, ItemID.Count);
            Measure("バニラアイテムのみ", sample);
            Main.NewText("1");
        }
        else if (frameCounter == TIME2)
        {
            int modItemCount = ItemLoader.ItemCount - ItemID.Count;
            Item[] sample = CreateSample(ItemID.Count - modItemCount, ItemLoader.ItemCount);
            Measure("バニラとModアイテム半々", sample);
            Main.NewText("2");
        }
        else if (frameCounter == TIME3)
        {
            Item[] sample = CreateSample(ItemID.Count, ItemLoader.ItemCount);
            Measure("Modアイテムのみ", sample);
            Main.NewText("3");
        }
        else if (frameCounter == TIME4)
        {
            Item[] sample = CreateSample(itemAlphabetTypes);
            Measure("Modアイテムのみ (継承元が同じ)", sample);
            Main.NewText("4");
        }
    }

    // サンプルの作成 (完全ランダム)
    private static Item[] CreateSample(int minItemType, int maxItemType)
    {
        Item[] sample = new Item[SAMPLE_COUNT];
        for (var i = 0; i < sample.Length; i++)
        {
            sample[i] = new Item(Main.rand.Next(minItemType, maxItemType));
        }
        return sample;
    }
    // サンプルの作成 (List<int>内のアイテムを一定数(SAMPLE_TARGET_COUNT)入れたサンプル)
    private static Item[] CreateSample(int minItemType, int maxItemType, List<int> targets)
    {
        Item[] sample = new Item[SAMPLE_COUNT];
        HashSet<int> targetSampleIndexes = new HashSet<int>();
        while (targetSampleIndexes.Count < SAMPLE_TARGET_COUNT)
        {
            targetSampleIndexes.Add(Main.rand.Next(SAMPLE_COUNT));
        }
        for (var i = 0; i < sample.Length; i++)
        {
            if (targetSampleIndexes.Contains(i))
            {
                sample[i] = new Item(Main.rand.NextFromCollection(targets));
            }
            else
            {
                sample[i] = new Item(Main.rand.Next(minItemType, maxItemType));
                while (targets.Contains(sample[i].type))
                {
                    sample[i] = new Item(Main.rand.Next(minItemType, maxItemType));
                }
            }
        }
        return sample;
    }
    // サンプルの作成 (List<int>内のアイテムのみで構成されたサンプル)
    private static Item[] CreateSample(List<int> targets)
    {
        Item[] sample = new Item[SAMPLE_COUNT];
        for (var i = 0; i < sample.Length; i++)
        {
            sample[i] = new Item(Main.rand.NextFromCollection(targets));
        }
        return sample;
    }

    private void Measure(string label, Item[] sample)
    {
        Stopwatch sw = new Stopwatch();

        int callCountItemType = 0;
        sw.Start();
        for (var i = 0; i < sample.Length; i++)
        {
            // item.type == ModContent.ItemType<T>() を使用した処理
        }
        sw.Stop();
        TimeSpan resultItemType = sw.Elapsed;

        int callCountArray = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            // TestID.Sets.TestArray[item.type] (bool[]) を使用した処理
        }
        sw.Stop();
        TimeSpan resultArray = sw.Elapsed;

        int callCountPatternMatching = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            // パターン マッチング (item.ModItem is T) を使用した処理
        }
        sw.Stop();
        TimeSpan resultPatternMatching = sw.Elapsed;

        int callCountSet = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            // TestID.Sets.TestSet.Contains(item.type) (HashSet<int>) を使用した処理
        }
        sw.Stop();
        TimeSpan resultSet = sw.Elapsed;

        StringBuilder sb = new StringBuilder();
        sb.Append('\n');
        sb.Append(label);
        sb.Append('\n');
        sb.Append(TimeSpanToString("ItemType", resultItemType));
        sb.Append($" ({callCountItemType})");
        sb.Append('\n');
        sb.Append(TimeSpanToString("Array", resultArray));
        sb.Append($" ({callCountArray})");
        sb.Append('\n');
        sb.Append(TimeSpanToString("PatternMatching", resultPatternMatching));
        sb.Append($" ({callCountPatternMatching})");
        sb.Append('\n');
        sb.Append(TimeSpanToString("HashSet", resultSet));
        sb.Append($" ({callCountSet})");
        sb.Append('\n');
        sb.Append($"|{label}|{resultItemType.Milliseconds}ms|{resultArray.Milliseconds}ms|{resultPatternMatching.Milliseconds}ms|{resultSet.Milliseconds}ms|");
        Mod.Logger.Debug(sb.ToString());
    }

    private static string TimeSpanToString(string label, TimeSpan time)
    {
        return $"{label}: {time.Minutes}min {time.Seconds}s {time.Milliseconds}ms";
    }
}

計測 #1 (評価のみ)

とりあえず評価だけ(実処理無し)で計測してみる。

コード
internal class Tester : ModPlayer
{
    // 略

    private void Measure(string title, Item[] sample)
    {
        // 略

        int callCountItemType = 0;
        sw.Start();
        for (var i = 0; i < sample.Length; i++)
        {
            if (sample[i].type == ModContent.ItemType<ItemC>())
            {
            }
        }
        sw.Stop();
        TimeSpan resultItemType = sw.Elapsed;

        int callCountArray = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            if (TestID.Sets.TestArray[sample[i].type])
            {
            }
        }
        sw.Stop();
        TimeSpan resultArray = sw.Elapsed;

        int callCountPatternMatching = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            if (sample[i].ModItem is ItemC)
            {
            }
        }
        sw.Stop();
        TimeSpan resultPatternMatching = sw.Elapsed;

        int callCountSet = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            if (TestID.Sets.TestSet.Contains(sample[i].type))
            {
            }
        }
        sw.Stop();
        TimeSpan resultSet = sw.Elapsed;

        // 略
    }

    // 略
}

計測結果 #1

サンプル数は1000000(100万)。
計測は5回行った。

各結果

1回目

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 49ms 8ms 7ms 36ms
バニラとModアイテム半々 55ms 8ms 7ms 38ms
Modアイテムのみ 49ms 9ms 9ms 36ms
Modアイテムのみ (継承元が同じ) 20ms 7ms 7ms 15ms

2回目

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 48ms 8ms 7ms 37ms
バニラとModアイテム半々 55ms 7ms 7ms 37ms
Modアイテムのみ 53ms 8ms 7ms 36ms
Modアイテムのみ (継承元が同じ) 25ms 7ms 7ms 18ms

3回目

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 48ms 8ms 7ms 36ms
バニラとModアイテム半々 55ms 8ms 7ms 38ms
Modアイテムのみ 49ms 8ms 7ms 35ms
Modアイテムのみ (継承元が同じ) 25ms 9ms 8ms 19ms

4回目

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 48ms 8ms 7ms 36ms
バニラとModアイテム半々 59ms 8ms 8ms 38ms
Modアイテムのみ 48ms 8ms 7ms 34ms
Modアイテムのみ (継承元が同じ) 21ms 7ms 7ms 15ms

5回目

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 48ms 8ms 7ms 36ms
バニラとModアイテム半々 56ms 9ms 8ms 39ms
Modアイテムのみ 50ms 10ms 9ms 35ms
Modアイテムのみ (継承元が同じ) 29ms 8ms 7ms 21ms

平均

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 48.2ms 8ms 7ms 36.2ms
バニラとModアイテム半々 56ms 8ms 7.4ms 38ms
Modアイテムのみ 49.8ms 8.6ms 7.8ms 35.2ms
Modアイテムのみ (継承元が同じ) 24ms 7.6ms 7.2ms 17.6ms

各記録と平均の記録で大きな差異は無し。(各計測の中で特段速かったり遅かったりする結果は無い。)
速い順に並べると、item.ModItem is TboolArray[item.type]hashSet<int>.Contains(item.type)item.type == ModContent.ItemType<T>()である。

考察 #1

えっ、HashSet遅い...
そりゃそうだ。計算量がO(1)で速いというのはあくまで他コレクションでそれぞれのContains()を比べた時の話である。
bool[], is Tより約5倍ほど遅かった。そんなに遅いとは思っていなかった。
とは言え、HashSetにすることが全くの無意味というわけではなさそうだ。一応ItemTypeを使うよりは速い。

少し気になるのは「Modアイテムのみ (継承元が同じ)」のItemTypeの記録が他のサンプルより速いことだ。HashSetが速くなるのは感覚的に納得できるが、ItemTypeが速くなったのは謎だ。
ちなみに、この場合でもHashSetの方が速いことには変わりない。

計測 #2 (実処理あり)

そのアイテムかどうか判定だけしたいこともあるだろうが、私の場合は、継承/委譲元のクラスのメソッドの呼び出し等を行うことが多い。
ということで、型のキャスト含め実処理をして計測してみる。

「バニラとModアイテム半々」、「Modアイテムのみ」のサンプルには、実処理が必要なアイテムの数を、サンプルの1/2にする。
「バニラアイテムのみ」では0、「Modアイテムのみ (継承元が同じ)」ではサンプルの数だけ処理が行われるが、実環境的に近いためそのままにする。

また、今回継承元のクラスをメソッドを呼び出すということで、特定のクラスを継承している全てのアイテムを対象とするため、ItemTypeの呼び出しが4回行われることに留意。

コード
public class ItemAlphabet : ModItem
{
    // 略

    internal void Test(Player player)
    {
        player.Heal(1);
    }
}
internal class Tester : ModPlayer
{
    // 略

    public override void PreUpdate()
    {
        // 略

        else if (frameCounter == TIME2)
        {
            int modItemCount = ItemLoader.ItemCount - ItemID.Count;
            Item[] sample = CreateSample(ItemID.Count - modItemCount, ItemLoader.ItemCount, itemAlphabetTypes);
            Measure("バニラとModアイテム半々", sample);
            Main.NewText("2");
        }
        else if (frameCounter == TIME3)
        {
            Item[] sample = CreateSample(ItemID.Count, ItemLoader.ItemCount, itemAlphabetTypes);
            Measure("Modアイテムのみ", sample);
            Main.NewText("3");
        }

        // 略
    }

    // 略

    private void Measure(string label, Item[] sample)
    {
        // 略

        int callCountItemType = 0;
        sw.Start();
        for (var i = 0; i < sample.Length; i++)
        {
            Item item = sample[i];
            if (item.type == ModContent.ItemType<ItemA>() || item.type == ModContent.ItemType<ItemB>() || item.type == ModContent.ItemType<ItemC>() || item.type == ModContent.ItemType<ItemD>())
            {
                ItemAlphabet modItem = (ItemAlphabet)item.ModItem;
                modItem.Test(Player);
                callCountItemType++;
            }
        }
        sw.Stop();
        TimeSpan resultItemType = sw.Elapsed;

        int callCountArray = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            Item item = sample[i];
            if (TestID.Sets.TestArray[item.type])
            {
                ItemAlphabet modItem = (ItemAlphabet)item.ModItem;
                modItem.Test(Player);
                callCountArray++;
            }
        }
        sw.Stop();
        TimeSpan resultArray = sw.Elapsed;

        int callCountPatternMatching = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            Item item = sample[i];
            if (item.ModItem is ItemAlphabet modItem)
            {
                modItem.Test(Player);
                callCountPatternMatching++;
            }
        }
        sw.Stop();
        TimeSpan resultPatternMatching = sw.Elapsed;

        int callCountSet = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            Item item = sample[i];
            if (TestID.Sets.TestSet.Contains(item.type))
            {
                ItemAlphabet modItem = (ItemAlphabet)item.ModItem;
                modItem.Test(Player);
                callCountSet++;
            }
        }
        sw.Stop();
        TimeSpan resultSet = sw.Elapsed;

        // 略
    }

    // 略

計測結果 #2

サンプル数は1000000(100万)、内 処理を行うアイテムは半分の500000(50万)。
計測は5回行った。

各結果

1回目

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 122ms 25ms 17ms 51ms
バニラとModアイテム半々 207ms 131ms 148ms 168ms
Modアイテムのみ 169ms 104ms 107ms 129ms
Modアイテムのみ (継承元が同じ) 155ms 114ms 104ms 124ms

2回目

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 119ms 24ms 17ms 49ms
バニラとModアイテム半々 199ms 128ms 148ms 163ms
Modアイテムのみ 180ms 112ms 115ms 140ms
Modアイテムのみ (継承元が同じ) 137ms 93ms 89ms 103ms

3回目

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 118ms 25ms 17ms 49ms
バニラとModアイテム半々 204ms 130ms 147ms 166ms
Modアイテムのみ 168ms 102ms 107ms 128ms
Modアイテムのみ (継承元が同じ) 144ms 100ms 95ms 111ms

4回目

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 118ms 24ms 17ms 49ms
バニラとModアイテム半々 217ms 138ms 150ms 175ms
Modアイテムのみ 169ms 105ms 106ms 128ms
Modアイテムのみ (継承元が同じ) 147ms 102ms 96ms 113ms

5回目

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 119ms 24ms 17ms 50ms
バニラとModアイテム半々 206ms 131ms 148ms 166ms
Modアイテムのみ 168ms 103ms 106ms 127ms
Modアイテムのみ (継承元が同じ) 145ms 102ms 96ms 111ms

平均

サンプルの種類 ItemType bool[] is T HashSet
バニラアイテムのみ 119.2ms 24.4ms 17ms 49.6ms
バニラとModアイテム半々 206.6ms 131.6ms 148.2ms 167.6ms
Modアイテムのみ 170.8ms 105.2ms 108.2ms 130.4ms
Modアイテムのみ (継承元が同じ) 145.6ms 102.2ms 96ms 112.4ms

各記録と平均の記録で大きな差異は無し。(各計測の中で特段速かったり遅かったりする結果は無い。)
速い順に並べると、item.ModItem is TboolArray[item.type]hashSet<int>.Contains(item.type)item.type == ModContent.ItemType<T>()と、計測#1の時とほぼ変わらない。
ただし、「バニラとModアイテム半々」「Modアイテムのみ」のサンプルではitem.ModItem is TboolArray[item.type]の速い順が入れ替わる。

考察 #2

処理分の時間が増えて差異が減ったように見えるが、やはり(ItemType, Hashset)と(is Tbool[])の間には超えられない壁がありそうだ。

処理しないもののみ/処理するもののみの「バニラアイテムのみ」「Modアイテムのみ (継承元が同じ)」ではbool[]よりもis Tの方が速く、残りの処理するしないが切り替わる「バニラとModアイテム半々」「Modアイテムのみ」ではis Tよりもbool[]の方が速い。
あまりアイテムが切り替わらない場合は、is T、切り替えが激しい場合はboolが最速と見える。
ただし、今回の想定はインベントリのホットバーであるため、どちらが最適かは一様に言えない。

計測 #EX-1 (抽象クラス vs インターフェース)

折角なので、抽象クラスのメソッドを呼び出すのと、インターフェースのメソッドを呼び出すの、どちらが速いか計測してみる。
処理は共通。

コード
// 本当は I から始めなきゃいけないけど
// 終わったらちゃちゃっと消せるようにしたいから
// Testから始まってるよ 許してね
internal interface TestInterface
{
    public void Test(Player player);
}
public class ItemAlphabet : ModItem, TestInterface
{
    // 略

    internal void Test(Player player)
    {
        player.Heal(1);
    }
}
internal class Tester : ModPlayer
{
    // 略

    private void Measure(string label, Item[] sample)
    {
        Stopwatch sw = new Stopwatch();

        int callCountArrayAbstractClass = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            Item item = sample[i];
            if (TestID.Sets.TestArray[item.type])
            {
                ItemAlphabet modItem = (ItemAlphabet)item.ModItem;
                modItem.Test(Player);
                callCountArrayAbstractClass++;
            }
        }
        sw.Stop();
        TimeSpan resultArrayAbstractClass = sw.Elapsed;

        int callCountArrayInterface = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            Item item = sample[i];
            if (TestID.Sets.TestArray[item.type])
            {
                TestInterface modItem = (TestInterface)item.ModItem;
                modItem.Test(Player);
                callCountArrayInterface++;
            }
        }
        sw.Stop();
        TimeSpan resultArrayInterface = sw.Elapsed;

        int callCountPatternMatchingAbstractClass = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            Item item = sample[i];
            if (item.ModItem is ItemAlphabet modItem)
            {
                modItem.Test(Player);
                callCountPatternMatchingAbstractClass++;
            }
        }
        sw.Stop();
        TimeSpan resultPatternMatchingAbstractClass = sw.Elapsed;

        int callCountPatternMatchingInterface = 0;
        sw.Restart();
        for (var i = 0; i < sample.Length; i++)
        {
            Item item = sample[i];
            if (item.ModItem is TestInterface modItem)
            {
                modItem.Test(Player);
                callCountPatternMatchingInterface++;
            }
        }
        sw.Stop();
        TimeSpan resultPatternMatchingInterface = sw.Elapsed;



        StringBuilder sb = new StringBuilder();
        sb.Append('\n');
        sb.Append(label);
        sb.Append('\n');
        sb.Append(TimeSpanToString("Array (抽象クラス)", resultArrayAbstractClass));
        sb.Append($" ({callCountArrayAbstractClass})");
        sb.Append('\n');
        sb.Append(TimeSpanToString("Array (インターフェース)", resultArrayInterface));
        sb.Append($" ({callCountArrayInterface})");
        sb.Append('\n');
        sb.Append(TimeSpanToString("PatternMatching (抽象クラス)", resultPatternMatchingAbstractClass));
        sb.Append($" ({callCountPatternMatchingAbstractClass})");
        sb.Append('\n');
        sb.Append(TimeSpanToString("PatternMatching (インターフェース)", resultPatternMatchingInterface));
        sb.Append($" ({callCountPatternMatchingInterface})");
        sb.Append('\n');
        sb.Append($"|{label}|{resultArrayAbstractClass.Milliseconds}ms|{resultArrayInterface.Milliseconds}ms|{resultPatternMatchingAbstractClass.Milliseconds}ms|{resultPatternMatchingInterface.Milliseconds}ms|");
        Mod.Logger.Debug(sb.ToString());
    }

    // 略
}

計測結果 #EX-1

サンプル数は1000000(100万)、内 処理を行うアイテムは半分の500000(50万)。
計測は5回行った。

各結果

1回目

サンプルの種類 bool[](A) bool[](I) is T(A) is T(I)
バニラアイテムのみ 26ms 10ms 17ms 17ms
バニラとModアイテム半々 124ms 135ms 160ms 183ms
Modアイテムのみ 109ms 114ms 126ms 129ms
Modアイテムのみ (継承元が同じ) 96ms 102ms 194ms 99ms

2回目

サンプルの種類 bool[](A) bool[](I) is T(A) is T(I)
バニラアイテムのみ 24ms 9ms 17ms 17ms
バニラとModアイテム半々 117ms 121ms 150ms 158ms
Modアイテムのみ 112ms 117ms 127ms 127ms
Modアイテムのみ (継承元が同じ) 98ms 103ms 196ms 101ms

3回目

サンプルの種類 bool[](A) bool[](I) is T(A) is T(I)
バニラアイテムのみ 24ms 10ms 17ms 17ms
バニラとModアイテム半々 132ms 137ms 165ms 174ms
Modアイテムのみ 109ms 102ms 122ms 114ms
Modアイテムのみ (継承元が同じ) 137ms 140ms 201ms 127ms

4回目

サンプルの種類 bool[](A) bool[](I) is T(A) is T(I)
バニラアイテムのみ 25ms 10ms 17ms 17ms
バニラとModアイテム半々 120ms 138ms 154ms 173ms
Modアイテムのみ 110ms 115ms 133ms 122ms
Modアイテムのみ (継承元が同じ) 109ms 113ms 195ms 107ms

5回目

サンプルの種類 bool[](A) bool[](I) is T(A) is T(I)
バニラアイテムのみ 24ms 10ms 17ms 17ms
バニラとModアイテム半々 130ms 133ms 157ms 173ms
Modアイテムのみ 122ms 123ms 140ms 142ms
Modアイテムのみ (継承元が同じ) 143ms 147ms 210ms 134ms

平均

サンプルの種類 bool[](A) bool[](I) is T(A) is T(I)
バニラアイテムのみ 24.6ms 9.8ms 17ms 17ms
バニラとModアイテム半々 124.6ms 132.8ms 157.2ms 172.2ms
Modアイテムのみ 112.4ms 114.2ms 129.6ms 126.8ms
Modアイテムのみ (継承元が同じ) 116.6ms 121ms 199.2ms 113.6ms

※ "(A)"は抽象クラス、"(I)"はインターフェース

考察 #EX-1

なんか無かったことにしたいくらい何とも言えない結果。
どうしてこうなった???

結論

  • item.type == ModContent.ItemType<T>()はやっぱり遅かった
  • item.ModItem is TboolArray[item.type]を使う方が速い
  • アイテムIDもどきをやるならHashSet<int>ではなく、バニラと同じItemID.Sets.Factory.CreateBoolSet()で作ったbool[]の方が良い

書き直しの時間だぁ

余談

Terraria v1.4.4にあたるtModLoaderではModContent.XType<T>()でキャッシュを取っているらしいので、この結果より速い可能性はある。調べる気は無いが、もしかしたらHashSet<int>が悪手になってる可能性が...?

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?