Greg 的配置文件结果非常适合他所涵盖的确切场景,但有趣的是,在考虑许多不同因素时,不同方法的相对成本会发生巨大变化,包括被比较的类型数量、相对频率和基础数据中的任何模式.
简单的答案是,没有人能告诉您在您的特定场景中性能差异是什么,您需要在自己的系统中以不同的方式测量性能以获得准确的答案。
If/Else 链是用于少量类型比较的有效方法,或者如果您可以可靠地预测哪些类型将构成您看到的大多数类型。该方法的潜在问题是,随着类型数量的增加,必须执行的比较次数也会增加。
如果我执行以下操作:
int value = 25124;
if(value == 0) ...
else if (value == 1) ...
else if (value == 2) ...
...
else if (value == 25124) ...
在输入正确的块之前,必须评估前面的每个 if 条件。另一方面
switch(value) {
case 0:...break;
case 1:...break;
case 2:...break;
...
case 25124:...break;
}
将执行一个简单的跳转到正确的代码位。
在您的示例中变得更复杂的地方是您的其他方法使用字符串而不是整数的开关,这变得有点复杂。在低级别上,字符串不能像整数值那样被打开,所以 C# 编译器做了一些魔术来为你工作。
如果 switch 语句“足够小”(编译器会自动执行它认为最好的操作),则打开字符串会生成与 if/else 链相同的代码。
switch(someString) {
case "Foo": DoFoo(); break;
case "Bar": DoBar(); break;
default: DoOther; break;
}
是相同的:
if(someString == "Foo") {
DoFoo();
} else if(someString == "Bar") {
DoBar();
} else {
DoOther();
}
一旦字典中的项目列表变得“足够大”,编译器将自动创建一个内部字典,该字典从开关中的字符串映射到整数索引,然后基于该索引进行开关。
它看起来像这样(想象一下比我要费心输入的条目更多)
静态字段在“隐藏”位置定义,该位置与包含类型 switch 语句的类相关联,Dictionary<string, int>
并给定一个错位名称
//Make sure the dictionary is loaded
if(theDictionary == null) {
//This is simplified for clarity, the actual implementation is more complex
// in order to ensure thread safety
theDictionary = new Dictionary<string,int>();
theDictionary["Foo"] = 0;
theDictionary["Bar"] = 1;
}
int switchIndex;
if(theDictionary.TryGetValue(someString, out switchIndex)) {
switch(switchIndex) {
case 0: DoFoo(); break;
case 1: DoBar(); break;
}
} else {
DoOther();
}
在我刚刚运行的一些快速测试中,If/Else 方法的速度大约是 3 种不同类型(其中类型随机分布)的 switch 的 3 倍。在 25 种类型中,切换速度稍快(16%),在 50 种类型中,切换速度是原来的两倍多。
如果您要打开大量类型,我建议使用第三种方法:
private delegate void NodeHandler(ChildNode node);
static Dictionary<RuntimeTypeHandle, NodeHandler> TypeHandleSwitcher = CreateSwitcher();
private static Dictionary<RuntimeTypeHandle, NodeHandler> CreateSwitcher()
{
var ret = new Dictionary<RuntimeTypeHandle, NodeHandler>();
ret[typeof(Bob).TypeHandle] = HandleBob;
ret[typeof(Jill).TypeHandle] = HandleJill;
ret[typeof(Marko).TypeHandle] = HandleMarko;
return ret;
}
void HandleChildNode(ChildNode node)
{
NodeHandler handler;
if (TaskHandleSwitcher.TryGetValue(Type.GetRuntimeType(node), out handler))
{
handler(node);
}
else
{
//Unexpected type...
}
}
这与 Ted Elliot 建议的类似,但使用运行时类型句柄而不是完整类型对象避免了通过反射加载类型对象的开销。
以下是我机器上的一些快速计时:
使用 5,000,000 个数据元素(模式=随机)和 5 种类型测试 3 次迭代
最佳方法时间百分比
如果/否则 179.67 100.00
类型句柄字典 321.33 178.85
类型字典 377.67 210.20
开关 492.67 274.21
使用 5,000,000 个数据元素(模式=随机)和 10 种类型测试 3 次迭代
最佳方法时间百分比
如果/否则 271.33 100.00
类型句柄字典 312.00 114.99
类型字典 374.33 137.96
开关 490.33 180.71
使用 5,000,000 个数据元素(模式=随机)和 15 种类型测试 3 次迭代
最佳方法时间百分比
类型句柄字典 312.00 100.00
如果/否则 369.00 118.27
类型字典 371.67 119.12
开关 491.67 157.59
使用 5,000,000 个数据元素(模式=随机)和 20 种类型测试 3 次迭代
最佳方法时间百分比
类型句柄字典 335.33 100.00
类型字典 373.00 111.23
如果/否则 462.67 137.97
开关 490.33 146.22
使用 5,000,000 个数据元素(模式=随机)和 25 种类型测试 3 次迭代
最佳方法时间百分比
类型句柄字典 319.33 100.00
类型字典 371.00 116.18
开关 483.00 151.25
如果/否则 562.00 175.99
使用 5,000,000 个数据元素(模式=随机)和 50 种类型测试 3 次迭代
最佳方法时间百分比
类型句柄字典 319.67 100.00
类型字典 376.67 117.83
开关 453.33 141.81
如果/否则 1,032.67 323.04
至少在我的机器上,当用作方法输入的类型的分布是随机的时,类型句柄字典方法在超过 15 种不同类型的任何情况下都胜过所有其他方法。
另一方面,如果输入完全由在 if/else 链中首先检查的类型组成,则该方法要快得多:
使用 5,000,000 个数据元素(模式=UniformFirst)和 50 种类型测试 3 次迭代
最佳方法时间百分比
如果/否则 39.00 100.00
类型句柄字典 317.33 813.68
类型字典 396.00 1,015.38
开关 403.00 1,033.33
相反,如果输入始终是 if/else 链中的最后一件事,则会产生相反的效果:
使用 5,000,000 个数据元素(mode=UniformLast)和 50 种类型测试 3 次迭代
最佳方法时间百分比
类型句柄字典 317.67 100.00
开关 354.33 111.54
类型字典 377.67 118.89
如果/否则 1,907.67 600.52
如果您可以对您的输入做出一些假设,您可能会从混合方法中获得最佳性能,在该方法中您对最常见的少数类型执行 if/else 检查,如果这些方法失败,则回退到字典驱动的方法。