一尘不染

什么更快,打开字符串或其他类型?

c#

可以说我可以选择根据字符串比较来确定要采用的代码路径,也可以根据类型来确定:

哪个更快,为什么?

switch(childNode.Name)
{
    case "Bob":
      break;
    case "Jill":
      break;
    case "Marko":
      break;
}

if(childNode is Bob)
{
}
elseif(childNode is Jill)
{
}
else if(childNode is Marko)
{
}

更新:
我问这的主要原因是因为switch语句对于具体情况视作具体问题。例如,它不允许您使用变量,而只能使用常量,这些常量将被移至主程序集。我认为它有此限制是因为它正在执行一些时髦的操作。如果仅翻译为elseifs(如一位发布者所评论),那么为什么在case语句中不允许变量?

警告: 我正在优化。这种方法被称为 许多 在应用程序的缓慢一部分倍。


阅读 288

收藏
2020-05-19

共1个答案

一尘不染

Greg的个人资料结果对于他所涵盖的确切场景非常有用,但是有趣的是,当考虑许多不同因素(包括所比较类型的数量,相对数据的频率和基础数据中的任何模式)时,不同方法的相对成本发生了巨大变化。

简单的答案是,没有人能告诉您在特定情况下的性能差异,您将需要在自己的系统中以各种方式自己衡量性能以获得准确的答案。

If /
Else链是少数类型比较的一种有效方法,或者您可以可靠地预测哪种类型的类型将构成您看到的大多数类型的比较。该方法的潜在问题在于,随着类型数量的增加,必须执行的比较数量也随之增加。

如果我执行以下命令:

int value = 25124;
if(value == 0) ...
else if (value == 1) ...
else if (value == 2) ...
...
else if (value == 25124) ...

在输入正确的程序段之前,必须先评估每个先前的条件。另一方面

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种不同类型(类型随机分布)的开关的3倍。在25种类型下,开关速度要快一点点(16%);在50种类型上,开关速度要快两倍以上。

如果您要打开大量类型,建议使用第3种方法:

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个数据元素(mode = Random)和5种类型测试3次迭代
方法时间最佳值的百分比
如果/其他179.67 100.00
类型句柄字典321.33 178.85
类型字典377.67 210.20
开关492.67 274.21

使用5,000,000个数据元素(mode = Random)和10种类型测试3次迭代
方法时间最佳值的百分比
如果/否则271.33 100.00
类型句柄词典312.00 114.99
类型字典374.33 137.96
开关490.33 180.71

使用5,000,000个数据元素(mode = Random)和15种类型测试3次迭代
方法时间最佳值的百分比
类型句柄字典312.00 100.00
如果/否则369.00 118.27
类型字典371.67 119.12
开关491.67 157.59

使用5,000,000个数据元素(mode = Random)和20种类型测试3次迭代
方法时间最佳值的百分比
类型句柄字典335.33 100.00
类型字典373.00 111.23
如果/否则462.67 137.97
开关490.33 146.22

使用5,000,000个数据元素(mode = Random)和25种类型测试3次迭代
方法时间最佳值的百分比
类型句柄词典319.33 100.00
类型字典371.00 116.18
开关483.00 151.25
如果/其他562.00 175.99

使用5,000,000个数据元素(mode = Random)和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个数据元素(mode = 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检查,然后在失败的情况下退回到字典驱动的方法。

2020-05-19