【问题标题】:What is quicker, switch on string or elseif on type?什么更快,打开字符串或 elseif 类型?
【发布时间】:2010-09-10 19:02:43
【问题描述】:

假设我可以选择根据字符串比较或判断类型来识别要采用的代码路径:

哪个更快,为什么?

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 语句对于什么是 case 非常陌生。例如,它不允许您使用变量,只能使用移动到主程序集中的常量。我认为它有这个限制是因为它正在做一些时髦的事情。如果它只是翻译成 elseifs(正如一位发帖人评论的那样),那么为什么我们不允许在 case 语句中使用变量?

警告:我正在进行后期优化。此方法在应用程序的慢速部分被调用多次次。

【问题讨论】:

标签: c# performance


【解决方案1】:

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

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

它看起来像这样(想象一下比我要费心输入更多的条目)

静态字段定义在“隐藏”位置,该位置与包含Dictionary<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 的建议,但使用运行时类型句柄而不是完整类型对象避免了通过反射加载类型对象的开销。

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

使用 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 检查,然后在出现以下情况时回退到字典驱动的方法那些失败了。

【讨论】:

  • 抱歉,我之前错过了这个回复。绝对值得成为顶级。
  • 这可能是我在 SO 上看到的最好的答案。天哪……竖起大拇指!
【解决方案2】:

首先,您是在比较苹果和橙子。您首先需要比较 switch on type 与 switch on string,然后比较 if on type 与 if on string,然后比较获胜者。

其次,这就是面向对象的设计目的。在支持 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
    }
}

然后,您只需调用 childNode.Action(),而不是执行 switch 语句

【讨论】:

  • (仅次于可读性和可维护性)一个有趣的问题是,与其他两种方法相比,这种方法的性能如何。注意:您也会考虑选择和实例化 Node 的实现部分的性能(例如工厂)。
【解决方案3】:

我刚刚实现了一个快速测试应用程序并使用 ANTS 4 对其进行了分析。
规格:.Net 3.5 sp1 in 32bit Windows XP,发布模式内置代码。

300 万次测试:

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

此外,switch 语句结果显示(不出所料)更长的名称需要更长的时间。

100 万次测试

  • 鲍勃: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";}}

【讨论】:

  • 这很有意义,因为切换是使用字符串完成的,您必须考虑字符串转换开销。如果每个类都有一个枚举呢?
  • “Bob”更快是因为它更短还是因为它是第一个?
【解决方案4】:

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

【讨论】:

  • 然而,类型比较也更容易维护。 -- 尽量不要过早优化。
  • 这有助于说明所写的内容并不完全是执行的内容,但在暗示无法优化 IF 时会产生误导。我不是优化器专家,但我正在研究 Reflector 中的一个类,其中带有只读变量的 If/ElseIf 在 IL 中与 SWITCH 相同。
  • 一个 if-else-if 结构确实可以被编译器转换为 switch 语句。为什么不能呢?
【解决方案5】:

如果你已经制作了类,我建议使用策略设计模式而不是 switch 或 elseif。

【讨论】:

  • 这是一个很好的建议!让对象自己决定它需要做什么。
  • 这样会更优雅,并且可以省去所有这些麻烦。然而,要做到这一点需要在这个领域进行大量的重构,并且只能作为最后的手段。但总的来说,我同意你的看法。 :D
【解决方案6】:

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

【讨论】:

    【解决方案7】:

    除非你已经写了这个并且发现你有性能问题,否则我不会担心哪个更快。选择更具可读性的那个。请记住,“过早的优化是万恶之源。” - 唐纳德·高德纳

    【讨论】:

    • 除非我们没有过早地进行优化。我们正在后期优化。对不起,但是-1。
    • 这里的答案不仅是为了提问者的利益,也是为了以后出现的任何人。人们需要意识到,出于性能原因提前做出此决定并不是最好的方法。
    • 哦,太好了,现在表明该问题无关紧要的答案首先是实际尝试回答该问题的所有其他答案。伙计们! :P
    • @Quarrelsome:警告不要过早优化与任何优化问题都相关,除非您指定您已经对代码进行了分析并且发现它太慢了。把它放在你的问题中,我的答案可能会停止被投票。
    • 考虑到过早优化的普遍性,是的。
    【解决方案8】:

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

    这里的难点在于它的使用已经泛化为“字符串”类型,显然不能用作索引,并且 SWITCH 构造的所有优点都丢失了。

    如果速度是您的预期目标,那么问题不是您的代码,而是您的数据结构。如果“名称”空间像您展示的那样简单,最好将其编码为整数值(例如,在创建数据时),并在“应用程序的慢速部分多次”中使用此整数。

    【讨论】:

      【解决方案9】:

      如果您打开的类型是原始 .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
      }
      

      【讨论】:

      • 可爱。 :) 知道委托调用是否会对性能产生任何连锁反应吗?
      【解决方案10】:

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

      【讨论】:

      • CLR 不会在 switch 语句中执行任何时髦的技巧吗?如果它只是转换为 else ifs,为什么它会强制你只使用常量作为 case 语句而不是变量?
      • 如果 switch 语句使用基本类型,CLR 将执行一些时髦的技巧,但是这里每种情况都需要进行字符串比较,因此几乎没有优化空间。
      • C# 不会将基于字符串的开关编译为字符串比较。由于 case 标签必须是文字,因此它使用了一些技巧,例如实习切换变量、打开哈希码然后检查对象身份(这将由于实习而起作用)以确保匹配正确。
      • oOo 现在这很有趣。所以 CLR 为它不为其他类型创建的字符串创建了一个例外?那么这是否意味着它等同于else ifs?
      【解决方案11】:

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

      实际上,如果您比较典型的 3 到 10 个(左右)语句,我严重怀疑使用其中一个或另一个是否有任何真正的性能提升。

      正如 Chris 已经说过的,追求可读性: What is quicker, switch on string or elseif on type?

      【讨论】:

        【解决方案12】:

        我认为这里的主要性能问题是,在 switch 块中,您比较字符串,而在 if-else 块中,您检查类型......这两个不一样,因此,我d 说您“将土豆与香蕉进行比较”。

        我先比较一下:

        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")
        {}
        

        【讨论】:

        • 您好,感谢您的回复!这实际上是我所拥有的场景。我们可以使用唯一的 id(字符串)或对象类型来区分这些对象。
        【解决方案13】:

        我不确定正确的设计对于多态性来说能有多快。

        interface INode
        {
            void Action;
        }
        
        class Bob : INode
        {
            public void Action
            {
        
            }
        }
        
        class Jill : INode
        {
            public void Action
            {
        
            }
        }
        
        class Marko : INode
        {
            public void Action
            {
        
            }
        }
        
        //Your function:
        void Do(INode childNode)
        {
            childNode.Action();
        }
        

        看看你的 switch 语句做了什么会更好。如果您的函数实际上与对该类型的操作无关,那么您可以为每种类型定义一个枚举。

        enum NodeType { Bob, Jill, Marko, Default }
        
        interface INode
        {
            NodeType Node { get; };
        }
        
        class Bob : INode
        {
            public NodeType Node { get { return NodeType.Bob; } }
        }
        
        class Jill : INode
        {
            public NodeType Node { get { return NodeType.Jill; } }
        }
        
        class Marko : INode
        {
            public NodeType Node { get { return NodeType.Marko; } }
        }
        
        //Your function:
        void Do(INode childNode)
        {
            switch(childNode.Node)
            {
                case Bob:
                  break;
                case Jill:
                  break;
                case Marko:
                  break;
                Default:
                  throw new ArgumentException();
            }
        }
        

        我认为这必须比所讨论的两种方法都快。您可能想尝试抽象类路由if nanoseconds does matter for you

        【讨论】:

          【解决方案14】:

          我创建了一个小控制台来展示我的解决方案,只是为了突出速度差异。我使用了不同的字符串哈希算法,因为证书版本在运行时对我来说会变慢,并且不太可能出现重复,如果是这样,我的 switch 语句将失败(直到现在从未发生过)。我独特的哈希扩展方法包含在下面的代码中。

          我将在 695 滴答声中任意取 29 个滴答声,特别是在使用关键代码时。

          使用来自给定数据库的一组字符串,您可以创建一个小型应用程序来在给定文件中创建常量,以供您在代码中使用,如果添加了值,您只需重新运行批处理并生成常量并被溶液吸收。

            public static class StringExtention
              {
                  public static long ToUniqueHash(this string text)
                  {
                      long value = 0;
                      var array = text.ToCharArray();
                      unchecked
                      {
                          for (int i = 0; i < array.Length; i++)
                          {
                              value = (value * 397) ^ array[i].GetHashCode();
                              value = (value * 397) ^ i;
                          }
                          return value;
                      }
                  }
              }
          
              public class AccountTypes
              {
          
                  static void Main()
                  {
                      var sb = new StringBuilder();
          
                      sb.AppendLine($"const long ACCOUNT_TYPE = {"AccountType".ToUniqueHash()};");
                      sb.AppendLine($"const long NET_LIQUIDATION = {"NetLiquidation".ToUniqueHash()};");
                      sb.AppendLine($"const long TOTAL_CASH_VALUE = {"TotalCashValue".ToUniqueHash()};");
                      sb.AppendLine($"const long SETTLED_CASH = {"SettledCash".ToUniqueHash()};");
                      sb.AppendLine($"const long ACCRUED_CASH = {"AccruedCash".ToUniqueHash()};");
                      sb.AppendLine($"const long BUYING_POWER = {"BuyingPower".ToUniqueHash()};");
                      sb.AppendLine($"const long EQUITY_WITH_LOAN_VALUE = {"EquityWithLoanValue".ToUniqueHash()};");
                      sb.AppendLine($"const long PREVIOUS_EQUITY_WITH_LOAN_VALUE = {"PreviousEquityWithLoanValue".ToUniqueHash()};");
                      sb.AppendLine($"const long GROSS_POSITION_VALUE ={ "GrossPositionValue".ToUniqueHash()};");
                      sb.AppendLine($"const long REQT_EQUITY = {"ReqTEquity".ToUniqueHash()};");
                      sb.AppendLine($"const long REQT_MARGIN = {"ReqTMargin".ToUniqueHash()};");
                      sb.AppendLine($"const long SPECIAL_MEMORANDUM_ACCOUNT = {"SMA".ToUniqueHash()};");
                      sb.AppendLine($"const long INIT_MARGIN_REQ = { "InitMarginReq".ToUniqueHash()};");
                      sb.AppendLine($"const long MAINT_MARGIN_REQ = {"MaintMarginReq".ToUniqueHash()};");
                      sb.AppendLine($"const long AVAILABLE_FUNDS = {"AvailableFunds".ToUniqueHash()};");
                      sb.AppendLine($"const long EXCESS_LIQUIDITY = {"ExcessLiquidity".ToUniqueHash()};");
                      sb.AppendLine($"const long CUSHION = {"Cushion".ToUniqueHash()};");
                      sb.AppendLine($"const long FULL_INIT_MARGIN_REQ = {"FullInitMarginReq".ToUniqueHash()};");
                      sb.AppendLine($"const long FULL_MAINTMARGIN_REQ ={ "FullMaintMarginReq".ToUniqueHash()};");
                      sb.AppendLine($"const long FULL_AVAILABLE_FUNDS = {"FullAvailableFunds".ToUniqueHash()};");
                      sb.AppendLine($"const long FULL_EXCESS_LIQUIDITY ={ "FullExcessLiquidity".ToUniqueHash()};");
                      sb.AppendLine($"const long LOOK_AHEAD_INIT_MARGIN_REQ = {"LookAheadInitMarginReq".ToUniqueHash()};");
                      sb.AppendLine($"const long LOOK_AHEAD_MAINT_MARGIN_REQ = {"LookAheadMaintMarginReq".ToUniqueHash()};");
                      sb.AppendLine($"const long LOOK_AHEAD_AVAILABLE_FUNDS = {"LookAheadAvailableFunds".ToUniqueHash()};");
                      sb.AppendLine($"const long LOOK_AHEAD_EXCESS_LIQUIDITY = {"LookAheadExcessLiquidity".ToUniqueHash()};");
                      sb.AppendLine($"const long HIGHEST_SEVERITY = {"HighestSeverity".ToUniqueHash()};");
                      sb.AppendLine($"const long DAY_TRADES_REMAINING = {"DayTradesRemaining".ToUniqueHash()};");
                      sb.AppendLine($"const long LEVERAGE = {"Leverage".ToUniqueHash()};");
                      Console.WriteLine(sb.ToString());
          
                      Test();    
                  }    
          
                  public static void Test()
                  {
                      //generated constant values
                      const long ACCOUNT_TYPE = -3012481629590703298;
                      const long NET_LIQUIDATION = 5886477638280951639;
                      const long TOTAL_CASH_VALUE = 2715174589598334721;
                      const long SETTLED_CASH = 9013818865418133625;
                      const long ACCRUED_CASH = -1095823472425902515;
                      const long BUYING_POWER = -4447052054809609098;
                      const long EQUITY_WITH_LOAN_VALUE = -4088154623329785565;
                      const long PREVIOUS_EQUITY_WITH_LOAN_VALUE = 6224054330592996694;
                      const long GROSS_POSITION_VALUE = -7316842993788269735;
                      const long REQT_EQUITY = -7457439202928979430;
                      const long REQT_MARGIN = -7525806483981945115;
                      const long SPECIAL_MEMORANDUM_ACCOUNT = -1696406879233404584;
                      const long INIT_MARGIN_REQ = 4495254338330797326;
                      const long MAINT_MARGIN_REQ = 3923858659879350034;
                      const long AVAILABLE_FUNDS = 2736927433442081110;
                      const long EXCESS_LIQUIDITY = 5975045739561521360;
                      const long CUSHION = 5079153439662500166;
                      const long FULL_INIT_MARGIN_REQ = -6446443340724968443;
                      const long FULL_MAINTMARGIN_REQ = -8084126626285123011;
                      const long FULL_AVAILABLE_FUNDS = 1594040062751632873;
                      const long FULL_EXCESS_LIQUIDITY = -2360941491690082189;
                      const long LOOK_AHEAD_INIT_MARGIN_REQ = 5230305572167766821;
                      const long LOOK_AHEAD_MAINT_MARGIN_REQ = 4895875570930256738;
                      const long LOOK_AHEAD_AVAILABLE_FUNDS = -7687608210548571554;
                      const long LOOK_AHEAD_EXCESS_LIQUIDITY = -4299898188451362207;
                      const long HIGHEST_SEVERITY = 5831097798646393988;
                      const long DAY_TRADES_REMAINING = 3899479916235857560;
                      const long LEVERAGE = 1018053116254258495;
          
                      bool found = false;
                      var sValues = new string[] {
                        "AccountType"
                        ,"NetLiquidation"
                        ,"TotalCashValue"
                        ,"SettledCash"
                        ,"AccruedCash"
                        ,"BuyingPower"
                        ,"EquityWithLoanValue"
                        ,"PreviousEquityWithLoanValue"
                        ,"GrossPositionValue"
                        ,"ReqTEquity"
                        ,"ReqTMargin"
                        ,"SMA"
                        ,"InitMarginReq"
                        ,"MaintMarginReq"
                        ,"AvailableFunds"
                        ,"ExcessLiquidity"
                        ,"Cushion"
                        ,"FullInitMarginReq"
                        ,"FullMaintMarginReq"
                        ,"FullAvailableFunds"
                        ,"FullExcessLiquidity"
                        ,"LookAheadInitMarginReq"
                        ,"LookAheadMaintMarginReq"
                        ,"LookAheadAvailableFunds"
                        ,"LookAheadExcessLiquidity"
                        ,"HighestSeverity"
                        ,"DayTradesRemaining"
                        ,"Leverage"
                      };
          
                      long t1, t2;
                      var sw = System.Diagnostics.Stopwatch.StartNew();
                      foreach (var name in sValues)
                      {
                          switch (name)
                          {
                              case "AccountType": found = true; break;
                              case "NetLiquidation": found = true; break;
                              case "TotalCashValue": found = true; break;
                              case "SettledCash": found = true; break;
                              case "AccruedCash": found = true; break;
                              case "BuyingPower": found = true; break;
                              case "EquityWithLoanValue": found = true; break;
                              case "PreviousEquityWithLoanValue": found = true; break;
                              case "GrossPositionValue": found = true; break;
                              case "ReqTEquity": found = true; break;
                              case "ReqTMargin": found = true; break;
                              case "SMA": found = true; break;
                              case "InitMarginReq": found = true; break;
                              case "MaintMarginReq": found = true; break;
                              case "AvailableFunds": found = true; break;
                              case "ExcessLiquidity": found = true; break;
                              case "Cushion": found = true; break;
                              case "FullInitMarginReq": found = true; break;
                              case "FullMaintMarginReq": found = true; break;
                              case "FullAvailableFunds": found = true; break;
                              case "FullExcessLiquidity": found = true; break;
                              case "LookAheadInitMarginReq": found = true; break;
                              case "LookAheadMaintMarginReq": found = true; break;
                              case "LookAheadAvailableFunds": found = true; break;
                              case "LookAheadExcessLiquidity": found = true; break;
                              case "HighestSeverity": found = true; break;
                              case "DayTradesRemaining": found = true; break;
                              case "Leverage": found = true; break;
                              default: found = false; break;
                          }
          
                          if (!found)
                              throw new NotImplementedException();
                      }
                      t1 = sw.ElapsedTicks;
                      sw.Restart();
                      foreach (var name in sValues)
                      {
                          switch (name.ToUniqueHash())
                          {
                              case ACCOUNT_TYPE:
                                  found = true;
                                  break;
                              case NET_LIQUIDATION:
                                  found = true;
                                  break;
                              case TOTAL_CASH_VALUE:
                                  found = true;
                                  break;
                              case SETTLED_CASH:
                                  found = true;
                                  break;
                              case ACCRUED_CASH:
                                  found = true;
                                  break;
                              case BUYING_POWER:
                                  found = true;
                                  break;
                              case EQUITY_WITH_LOAN_VALUE:
                                  found = true;
                                  break;
                              case PREVIOUS_EQUITY_WITH_LOAN_VALUE:
                                  found = true;
                                  break;
                              case GROSS_POSITION_VALUE:
                                  found = true;
                                  break;
                              case REQT_EQUITY:
                                  found = true;
                                  break;
                              case REQT_MARGIN:
                                  found = true;
                                  break;
                              case SPECIAL_MEMORANDUM_ACCOUNT:
                                  found = true;
                                  break;
                              case INIT_MARGIN_REQ:
                                  found = true;
                                  break;
                              case MAINT_MARGIN_REQ:
                                  found = true;
                                  break;
                              case AVAILABLE_FUNDS:
                                  found = true;
                                  break;
                              case EXCESS_LIQUIDITY:
                                  found = true;
                                  break;
                              case CUSHION:
                                  found = true;
                                  break;
                              case FULL_INIT_MARGIN_REQ:
                                  found = true;
                                  break;
                              case FULL_MAINTMARGIN_REQ:
                                  found = true;
                                  break;
                              case FULL_AVAILABLE_FUNDS:
                                  found = true;
                                  break;
                              case FULL_EXCESS_LIQUIDITY:
                                  found = true;
                                  break;
                              case LOOK_AHEAD_INIT_MARGIN_REQ:
                                  found = true;
                                  break;
                              case LOOK_AHEAD_MAINT_MARGIN_REQ:
                                  found = true;
                                  break;
                              case LOOK_AHEAD_AVAILABLE_FUNDS:
                                  found = true;
                                  break;
                              case LOOK_AHEAD_EXCESS_LIQUIDITY:
                                  found = true;
                                  break;
                              case HIGHEST_SEVERITY:
                                  found = true;
                                  break;
                              case DAY_TRADES_REMAINING:
                                  found = true;
                                  break;
                              case LEVERAGE:
                                  found = true;
                                  break;
                              default:
                                  found = false;
                                  break;
                          }
          
                          if (!found)
                              throw new NotImplementedException();
                      }
                      t2 = sw.ElapsedTicks;
                      sw.Stop();
                      Console.WriteLine($"String switch:{t1:N0} long switch:{t2:N0}");
                      var faster = (t1 > t2) ? "Slower" : "faster";
                      Console.WriteLine($"String switch: is {faster} than long switch: by {Math.Abs(t1-t2)} Ticks");
                      Console.ReadLine();
          
                  }
          

          【讨论】:

            【解决方案15】:

            字符串比较总是完全依赖于运行时环境(除非字符串是静态分配的,尽管需要将它们相互比较是有争议的)。然而,类型比较可以通过动态或静态绑定来完成,无论哪种方式,对于运行时环境来说,它都比比较字符串中的单个字符更有效。

            【讨论】:

              【解决方案16】:

              String 上的开关肯定会编译为字符串比较(每种情况一个),它比类型比较慢(并且比用于 switch/case 的典型整数比较慢得多)?

              【讨论】:

                【解决方案17】:

                三个想法:

                1) 如果您要根据对象的类型做一些不同的事情,那么将这种行为移到这些类中可能是有意义的。然后,您只需调用 childNode.DoSomething(),而不是 switch 或 if-else。

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

                3) 在 if-else 设计中,您可以利用重新排序测试的优势。如果“吉尔”对象占通过那里的对象的 90%,请先对其进行测试。

                【讨论】:

                  【解决方案18】:

                  开关的一个问题是使用字符串,例如“Bob”,这将导致编译代码中出现更多的循环和行。生成的 IL 必须声明一个字符串,将其设置为“Bob”,然后在比较中使用它。因此,考虑到这一点,您的 IF 语句会运行得更快。

                  PS。 Aeon 的示例不起作用,因为您无法打开类型。 (不,我不知道究竟是为什么,但我们已经尝试过了,但它不起作用。它与变量类型有关)

                  如果您想对此进行测试,只需构建一个单独的应用程序并构建两个简单的方法来执行上面所写的操作,并使用 Ildasm.exe 之类的东西来查看 IL。您会注意到 IF 语句 Method 的 IL 中的行少了很多。

                  Ildasm 自带 VisualStudio...

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

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

                  【讨论】:

                    【解决方案19】:

                    请记住,分析器是您的朋友。大多数时候,任何猜测都是浪费时间。 顺便说一句,我对 JetBrains 的 dotTrace profiler 有很好的体验。

                    【讨论】:

                      【解决方案20】:

                      Switch on string 基本上被编译成一个 if-else-if 阶梯。尝试反编译一个简单的。在任何情况下,测试字符串相等性应该更便宜,因为它们是被实习的,所需要的只是参考检查。在可维护性方面做有意义的事情;如果您正在压缩字符串,请进行字符串切换。如果您根据类型进行选择,则类型阶梯更为合适。

                      【讨论】:

                        【解决方案21】:

                        我的做法有点不同, 您打开的字符串将是常量,因此您可以在编译时预测值。

                        在你的情况下,我会使用哈希值,这是一个 int 开关,你有 2 个选项,使用编译时常量或在运行时计算。

                        //somewhere in your code
                        static long _bob = "Bob".GetUniqueHashCode();
                        static long _jill = "Jill".GetUniqueHashCode();
                        static long _marko = "Marko".GeUniquetHashCode();
                        
                        void MyMethod()
                        {
                           ...
                           if(childNode.Tag==0)
                              childNode.Tag= childNode.Name.GetUniquetHashCode()
                        
                           switch(childNode.Tag)
                           {
                               case _bob :
                                break;
                               case _jill :
                                 break;
                               case _marko :
                                break;
                           }
                        }
                        

                        GetUniquetHashCode 的扩展方法可以是这样的:

                        public static class StringExtentions
                            {
                                /// <summary>
                                /// Return unique Int64 value for input string
                                /// </summary>
                                /// <param name="strText"></param>
                                /// <returns></returns>
                                public static Int64 GetUniquetHashCode(this string strText)
                                {
                                    Int64 hashCode = 0;
                                    if (!string.IsNullOrEmpty(strText))
                                    {
                                        //Unicode Encode Covering all character-set
                                        byte[] byteContents = Encoding.Unicode.GetBytes(strText);
                                        System.Security.Cryptography.SHA256 hash =  new System.Security.Cryptography.SHA256CryptoServiceProvider();
                                        byte[] hashText = hash.ComputeHash(byteContents);
                                        //32Byte hashText separate
                                        //hashCodeStart = 0~7  8Byte
                                        //hashCodeMedium = 8~23  8Byte
                                        //hashCodeEnd = 24~31  8Byte
                                        //and Fold
                                        Int64 hashCodeStart = BitConverter.ToInt64(hashText, 0);
                                        Int64 hashCodeMedium = BitConverter.ToInt64(hashText, 8);
                                        Int64 hashCodeEnd = BitConverter.ToInt64(hashText, 24);
                                        hashCode = hashCodeStart ^ hashCodeMedium ^ hashCodeEnd;
                                    }
                                    return (hashCode);
                                }
                        
                        
                            }
                        

                        此代码的来源已发布here 请注意,使用 Cryptography 很慢,您通常会在应用程序启动时预热支持的字符串,我将它们保存在静态字段中,因为它们不会改变并且与实例无关。请注意,我设置了节点对象的标记值,我可以使用任何属性或添加一个,只要确保这些与实际文本同步即可。

                        我在低延迟系统上工作,我的所有代码都以命令字符串的形式出现:值,命令:值....

                        现在命令都被称为 64 位整数值,因此像这样切换可以节省一些 CPU 时间。

                        【讨论】:

                          【解决方案22】:

                          我只是在这里阅读答案列表,并想分享this benchmark test,它将switch 构造与if-else 和三元? 运算符进行比较。

                          我喜欢that post 的地方在于它不仅比较单左结构(例如if-else),而且比较双层和三层结构(例如if-else-if-else)。

                          根据结果,if-else 构造是 8/9 测试用例中最快的; switch 构造在 5/9 测试用例中并列最快。

                          因此,如果您正在寻找速度,if-else 似乎是最快的方法。

                          【讨论】:

                            【解决方案23】:

                            我可能遗漏了一些东西,但你不能对类型而不是字符串做一个 switch 语句吗?也就是说,

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

                            【讨论】:

                            • 不,switch 操作对类型等对象不起作用。仅限“整数类型”
                            • 字符串不是整数类型!
                            • 那么为什么框架允许一个字符串而不是一个编译器错误的类型:“预期的整数类型的值”。允许你使用字符串,即使它们不是整数类型,这只是一个小技巧吗?
                            • 哦。好的,抱歉 :) 我不懂 c#,允许作为 if 条件有效的东西作为 switch 条件似乎是合乎逻辑的。
                            • @Quibblesome 这确实是一个技巧。 switch case 可以处理字符串,尽管它们不是整数类型
                            猜你喜欢
                            • 2022-01-18
                            • 1970-01-01
                            • 1970-01-01
                            • 2021-08-20
                            • 2019-11-21
                            • 2014-04-03
                            • 1970-01-01
                            • 2011-06-21
                            • 1970-01-01
                            相关资源
                            最近更新 更多