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 中的字符串映射到整数索引,然后基于该索引进行 switch。
它看起来像这样(想象一下比我打算打的更多的条目)
在包含 switch 语句的类中定义了一个静态字段,类型为 Dictionary<string, int>
,并赋予一个混淆的名称,该名称位于“隐藏”的位置。
if(theDictionary == null) {
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倍,对于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
{
}
}
这与Ted Elliot的建议类似,但使用运行时类型句柄而不是完整的类型对象可以避免通过反射加载类型对象的开销。
这是在我的机器上进行的一些快速计时:
测试3次迭代,使用5,000,000个数据元素(模式=随机)和5种类型
方法 时间 最优解的百分比
If/Else 179.67 100.00
TypeHandleDictionary 321.33 178.85
TypeDictionary 377.67 210.20
Switch 492.67 274.21
测试3次迭代,使用5,000,000个数据元素(模式=随机)和10种类型
方法 时间 最优解的百分比
If/Else 271.33 100.00
TypeHandleDictionary 312.00 114.99
TypeDictionary 374.33 137.96
Switch 490.33 180.71
测试3次迭代,使用5,000,000个数据元素(模式=随机)和15种类型
方法 时间 最优解的百分比
TypeHandleDictionary 312.00 100.00
If/Else 369.00 118.27
TypeDictionary 371.67 119.12
Switch 491.67 157.59
测试3次迭代,使用5,000,000个数据元素(模式=随机)和20种类型
方法 时间 最优解的百分比
TypeHandleDictionary 335.33 100.00
TypeDictionary 373.00 111.23
If/Else 462.67 137.97
Switch 490.33 146.22
测试3次迭代,使用5,000,000个数据元素(模式=随机)和25种类型
方法 时间 最优解的百分比
TypeHandleDictionary 319.33 100.00
TypeDictionary 371.00 116.18
Switch 483.00 151.25
If/Else 562.00 175.99
测试3次迭代,使用5,000,000个数据元素(模式=随机)和50种类型
方法 时间 最优解的百分比
TypeHandleDictionary 319.67 100.00
TypeDictionary 376.67 117.83
Switch 453.33 141.81
If/Else 1,032.67 323.04
在我的机器上,当输入到方法的类型分布是随机的时候,至少对于超过15种不同类型的处理方式,使用类型句柄字典的方法要比其他所有方法更好。
另一方面,如果输入完全由在if/else链中首先检查的类型组成,那么这个方法会快得多:
在5,000,000个数据元素(模式=UniformFirst)和50种类型的情况下,进行3次迭代测试
方法 时间 最佳状态百分比
If/Else 39.00 100.00
TypeHandleDictionary 317.33 813.68
TypeDictionary 396.00 1,015.38
Switch 403.00 1,033.33
相反,如果输入总是出现在if/else链的最后一个位置,将产生相反的效果:
在5,000,000个数据元素(模式=UniformLast)和50种类型的情况下,进行3次迭代测试
方法 时间 最佳状态百分比
TypeHandleDictionary 317.67 100.00
Switch 354.33 111.54
TypeDictionary 377.67 118.89
If/Else 1,907.67 600.52
如果您可以对输入进行一些假设,那么您可能会从混合方法中获得最佳性能,其中您对最常见的几种类型执行if/else检查,然后在这些失败时退回到基于字典的方法。
Marko
继承自Bob
,那么使用is
运算符将会返回true
,即使childNode
是Bob
、Jill
或Marco
的子类也是如此。 - Matthijs Wessels