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

时间:2020-03-06 14:22:19  来源:igfitidea点击:

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

哪个更快,为什么?

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语句中不允许使用变量?

警告:我正在优化。在应用程序的缓慢部分中多次调用此方法。

解决方案

switch()将编译为等效于一组ifs的代码。字符串比较将比类型比较慢得多。

除非我们已经写过这篇文章并且发现我们有性能问题,否则我不会担心哪个更快。选择可读性更强的一种。请记住,"过早的优化是万恶之源。"唐纳德·克努斯

字符串比较将始终完全依赖于运行时环境(除非静态分配了字符串,尽管需要将它们相互比较是有争议的)。但是,类型比较可以通过动态或者静态绑定来完成,这两种方式对运行时环境而言都比比较字符串中的各个字符更有效。

可以肯定的是,对String的切换将编译为一个String比较(每一种情况),它比类型比较慢(并且比用于switch / case的典型整数比较慢得多)?

我可能丢失了一些内容,但是我们不能在类型而不是字符串上执行switch语句吗?那是,

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

Switch语句比if-else-if梯形图的执行速度更快。这是由于编译器能够优化switch语句。在if-else-if阶梯的情况下,代码必须按照程序员确定的顺序处理每个if语句。但是,由于switch语句中的每种情况都不依赖较早的情况,因此编译器能够以提供最快执行速度的方式对测试进行重新排序。

如果我们已经完成了这些类,那么我建议我们使用策略设计模式,而不要使用switch或者elseif。

我记得在几本参考书中读到,if / else分支比switch语句要快。但是,对Blackwasp的一些研究表明,switch语句实际上更快:
http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

实际上,如果我们比较的是典型的3到10(或者大约10条)语句,我严重怀疑使用其中的一种会带来实际的性能提升。

就像克里斯已经说过的那样,为了提高可读性:
什么更快,打开字符串或者其他类型?

我认为这里的主要性能问题是,在switch块中,我们比较字符串,在if-else块中,我们检查类型...这两个是不相同的,因此,我会说在"将土豆与香蕉进行比较"。

我先比较一下:

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

if(childNode.Name == "Bob")
{}
else if(childNode.Name == "Jill")
{}
else if(childNode.Name == "Marko")
{}

开关的问题之一是使用字符串,例如" Bob",这将在编译后的代码中引起更多的循环和更多行。生成的IL将必须声明一个字符串,将其设置为" Bob",然后在比较中使用它。因此,请记住,IF语句将运行得更快。

PS。 Aeon的示例无法正常工作,因为我们无法打开"类型"。 (不,我不知道为什么会这样,但是我们已经尝试过了,这是行不通的。它与变量类型有关)

如果要对此进行测试,只需构建一个单独的应用程序,并构建两个简单的方法即可完成上面所述的操作,并使用类似Ildasm.exe的方法查看IL。我们会注意到IF语句Method的IL中的行要少得多。

Ildasm随附VisualStudio ...

ILDASM页面http://msdn.microsoft.com/zh-cn/library/f7dy01k1(VS.80).aspx

ILDASM教程http://msdn.microsoft.com/zh-cn/library/aa309387(VS.71).aspx

SWITCH构造最初是为整数数据而设计的。目的是将参数直接用作"调度表"(指针表)的索引。这样,将只有一个测试,然后直接启动到相关代码,而不是一系列测试。

此处的困难在于,它的使用已被普遍化为"字符串"类型,显然不能用作索引,并且SWITCH构造的所有优势都丢失了。

如果速度是预期目标,那么问题不在于代码,而在于数据结构。如果"名称"空间如我们所显示的那么简单,则最好将其编码为一个整数值(例如,创建数据时),并在"应用程序速度较慢的部分中多次使用"该整数。

如果我们要切换的类型是原始.NET类型,则可以使用Type.GetTypeCode(Type),但是如果它们是自定义类型,它们都将作为TypeCode.Object返回。

包含委托或者处理程序类的字典也可以工作。

Dictionary<Type, HandlerDelegate> handlers = new Dictionary<Type, HandlerDelegate>();
handlers[typeof(Bob)] = this.HandleBob;
handlers[typeof(Jill)] = this.HandleJill;
handlers[typeof(Marko)] = this.HandleMarko;

handlers[childNode.GetType()](childNode);
/// ...

private void HandleBob(Node childNode) {
    // code to handle Bob
}

三个想法:

1)如果要根据对象的类型执行不同的操作,则可以将这种行为移入那些类中。然后,我们只需调用childNode.DoSomething()来代替switch或者if-else。

2)比较类型将比字符串比较快得多。

3)在if-else设计​​中,我们可以利用对测试进行重新排序的优势。如果"吉尔"物体占通过该物体的物体的90%,请首先对其进行测试。

我刚刚实现了一个快速测试应用程序,并使用ANTS 4对其进行了概要分析。
规格:32位Windows XP中的.Net 3.5 sp1,代码以发布模式构建。

300万次测试:

  • 切换:1.842秒
  • 如果:0.344秒。

此外,switch语句的结果显示(毫不奇怪)较长的名称花费较长的时间。

一百万次测试

  • 鲍勃:0.612秒。
  • 吉尔:0.835秒。
  • 马可:1.093秒。

我看起来" If Else"更快,至少是我创建的场景。

class Program
{
    static void Main( string[] args )
    {
        Bob bob = new Bob();
        Jill jill = new Jill();
        Marko marko = new Marko();

        for( int i = 0; i < 1000000; i++ )
        {
            Test( bob );
            Test( jill );
            Test( marko );
        }
    }

    public static void Test( ChildNode childNode )
    {   
        TestSwitch( childNode );
        TestIfElse( childNode );
    }

    private static void TestIfElse( ChildNode childNode )
    {
        if( childNode is Bob ){}
        else if( childNode is Jill ){}
        else if( childNode is Marko ){}
    }

    private static void TestSwitch( ChildNode childNode )
    {
        switch( childNode.Name )
        {
            case "Bob":
                break;
            case "Jill":
                break;
            case "Marko":
                break;
        }
    }
}

class ChildNode { public string Name { get; set; } }

class Bob : ChildNode { public Bob(){ this.Name = "Bob"; }}

class Jill : ChildNode{public Jill(){this.Name = "Jill";}}

class Marko : ChildNode{public Marko(){this.Name = "Marko";}}

尝试对每个对象使用枚举,我们可以快速轻松地打开枚举。

请记住,探查器是朋友。大多数情况下,任何猜测都是在浪费时间。
顺便说一句,我在JetBrains的dotTrace Profiler方面有很好的经验。

接通字符串基本上被编译为if-else-if阶梯。尝试反编译一个简单的。在任何情况下,测试字符串的适用性都应该便宜一些,因为它们是经过检查的,所需要的只是参考检查。在可维护性方面做有意义的事情;如果要压缩字符串,请进行字符串切换。如果基于类型进行选择,则更适合使用类型阶梯。

首先,我们要比较苹果和橙子。我们首先需要比较启用类型与启用字符串,然后比较类型与启用字符串,然后比较获胜者。

其次,这是OO设计的目的。在支持OO的语言中,打开类型(任何类型)是一种代码味道,表明设计不良。解决方案是从具有抽象或者虚拟方法(或者类似构造,取决于语言)的通用基础中得出

例如。

class Node
{
    public virtual void Action()
    {
        // Perform default action
    }
}

class Bob : Node
{
    public override void Action()
    {
        // Perform action for Bill
    }
}

class Jill : Node
{
    public override void Action()
    {
        // Perform action for Jill
    }
}

然后,我们无需调用switch语句,而只需调用childNode.Action()

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;
}

将执行一次简单的跳转以跳转到正确的代码位。

在示例中,它变得更复杂的地方是其他方法使用了一个字符串开关,而不是整数,这会变得更加复杂。在较低的级别上,无法以与整数值相同的方式来打开字符串,因此Ccompiler会做一些魔术来使我们满意。

如果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();
}

一旦字典中的项目列表变得"足够大",编译器将自动创建一个内部字典,该字典从开关中的字符串映射到整数索引,然后基于该索引进行开关。

看起来像这样(想像一下,比我要打扰的条目还要多的条目)

在与包含类Dictionary &lt;string,int>的switch语句的类相关联的类的"隐藏"位置中定义了一个静态字段,并指定了错误的名称

//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种类型上,开关速度要快两倍以上。

如果我们要打开大量类型,我建议使用第三个方法:

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的建议类似,但是使用运行时类型句柄而不是完整类型对象避免了通过反射加载类型对象的开销。

这是我机器上的一些快速计时:

Testing 3 iterations with 5,000,000 data elements (mode=Random) and 5 types
Method                Time    % of optimal
If/Else               179.67  100.00
TypeHandleDictionary  321.33  178.85
TypeDictionary        377.67  210.20
Switch                492.67  274.21

Testing 3 iterations with 5,000,000 data elements (mode=Random) and 10 types
Method                Time    % of optimal
If/Else               271.33  100.00
TypeHandleDictionary  312.00  114.99
TypeDictionary        374.33  137.96
Switch                490.33  180.71

Testing 3 iterations with 5,000,000 data elements (mode=Random) and 15 types
Method                Time    % of optimal
TypeHandleDictionary  312.00  100.00
If/Else               369.00  118.27
TypeDictionary        371.67  119.12
Switch                491.67  157.59

Testing 3 iterations with 5,000,000 data elements (mode=Random) and 20 types
Method                Time    % of optimal
TypeHandleDictionary  335.33  100.00
TypeDictionary        373.00  111.23
If/Else               462.67  137.97
Switch                490.33  146.22

Testing 3 iterations with 5,000,000 data elements (mode=Random) and 25 types
Method                Time    % of optimal
TypeHandleDictionary  319.33  100.00
TypeDictionary        371.00  116.18
Switch                483.00  151.25
If/Else               562.00  175.99

Testing 3 iterations with 5,000,000 data elements (mode=Random) and 50 types
Method                Time      % of optimal
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链中首先检查的类型组成,则该方法要快得多:

Testing 3 iterations with 5,000,000 data elements (mode=UniformFirst) and 50 types
Method                Time    % of optimal
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链中的最后一件事,则会产生相反的效果:

Testing 3 iterations with 5,000,000 data elements (mode=UniformLast) and 50 types
Method                Time      % of optimal
TypeHandleDictionary  317.67    100.00
Switch                354.33    111.54
TypeDictionary        377.67    118.89
If/Else               1,907.67  600.52

如果可以对输入做出一些假设,则可以从混合方法中获得最佳性能,在该方法中,对最常见的几种类型执行if / else检查,然后在失败的情况下退回到字典驱动的方法。