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 T
、boolArray[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 T
、boolArray[item.type]
、hashSet<int>.Contains(item.type)
、item.type == ModContent.ItemType<T>()
と、計測#1の時とほぼ変わらない。
ただし、「バニラとModアイテム半々」「Modアイテムのみ」のサンプルではitem.ModItem is T
とboolArray[item.type]
の速い順が入れ替わる。
考察 #2
処理分の時間が増えて差異が減ったように見えるが、やはり(ItemType
, Hashset
)と(is T
とbool[]
)の間には超えられない壁がありそうだ。
処理しないもののみ/処理するもののみの「バニラアイテムのみ」「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 T
かboolArray[item.type]
を使う方が速い - アイテムIDもどきをやるなら
HashSet<int>
ではなく、バニラと同じItemID.Sets.Factory.CreateBoolSet()
で作ったbool[]
の方が良い
書き直しの時間だぁ
余談
Terraria v1.4.4にあたるtModLoaderではModContent.XType<T>()
でキャッシュを取っているらしいので、この結果より速い可能性はある。調べる気は無いが、もしかしたらHashSet<int>
が悪手になってる可能性が...?