【问题标题】:How would you make this switch statement as fast as possible?你如何使这个 switch 语句尽可能快?
【发布时间】:2009-12-03 04:02:57
【问题描述】:

2009-12-04 更新:有关此处发布的一些建议的分析结果,请参见下文!


问题

考虑以下非常无害、非常直接的方法,它使用switch 语句返回定义的枚举值:

public static MarketDataExchange GetMarketDataExchange(string ActivCode) {
    if (ActivCode == null) return MarketDataExchange.NONE;

    switch (ActivCode) {
        case "": return MarketDataExchange.NBBO;
        case "A": return MarketDataExchange.AMEX;
        case "B": return MarketDataExchange.BSE;
        case "BT": return MarketDataExchange.BATS;
        case "C": return MarketDataExchange.NSE;
        case "MW": return MarketDataExchange.CHX;
        case "N": return MarketDataExchange.NYSE;
        case "PA": return MarketDataExchange.ARCA;
        case "Q": return MarketDataExchange.NASDAQ;
        case "QD": return MarketDataExchange.NASDAQ_ADF;
        case "W": return MarketDataExchange.CBOE;
        case "X": return MarketDataExchange.PHLX;
        case "Y": return MarketDataExchange.DIRECTEDGE;
    }

    return MarketDataExchange.NONE;
}

我和我的同事今天讨论了一些关于如何真正使这种方法更快的想法,我们提出了一些有趣的修改,这些修改实际上确实显着提高了它的性能(当然,按比例来说)。我很想知道其他人能想到哪些我们可能没有想到的优化。

马上,让我提供一个简短的免责声明:这是为了有趣不是推动整个“优化还是不优化”辩论.也就是说,如果您认为自己属于那些教条主义地相信“过早的优化是万恶之源”的人,请注意我在一家高频交易公司工作,一切都需要绝对运行尽可能快——瓶颈与否。所以,即使我在 SO 上发布这个是为了乐趣,这也不仅仅是浪费时间。

另一个快速说明:我对两种答案感兴趣——那些假设每个输入都是有效的 ActivCode(上面的 switch 语句中的字符串之一),而那些不是。我几乎确信做出第一个假设可以进一步提高速度;无论如何,它为我们做了。但我知道无论哪种方式都可以改进。


结果

嗯,事实证明,迄今为止(我测试过的)最快的解决方案来自 João Angelo,他的建议实际上非常简单,但非常聪明。我和我的同事设计的解决方案(在尝试了几种方法之后,其中许多也是在这里想到的)排在第二位。我正要发布它,但事实证明 Mark Ransom 提出了完全相同的想法,所以请查看他的答案!

自从我运行这些测试后,其他一些用户发布了甚至更新的想法...我会在适当的时候测试它们,当我有更多的空闲时间时。

我在两台不同的机器上运行了这些测试:我家的个人电脑(一台运行 Windows 7 64 位、4 Gb RAM 的双核 Athlon)和我工作的开发机器(一台 2 Gb RAM 的双核 Athlon运行 Windows XP SP3)。显然,时代不同了。但是,相对时间,意思是每种方法与其他方法相比的方式是相同的。也就是说,最快的是两台机器上最快的等等。

现在来看结果。 (我在下面发布的时间来自我的家用电脑。)

但首先,作为参考——原始 switch 语句:
1000000 次运行:98.88 毫秒
平均:0.09888 微秒

迄今为止最快的优化:

  1. João Angelo 的想法是根据 ActivCode 字符串的哈希码为枚举分配值,然后将 ActivCode.GetHashCode() 直接封装为 MarketDataExchange
    1000000 次运行:23.64 毫秒
    平均:0.02364 微秒
    速度提升:329.90%

  2. 我的同事和我将ActivCode[0] 转换为int 并从启动时初始化的数组中检索适当的MarketDataExchange 的想法(Mark Ransom 提出了完全相同的想法):
    1000000 次运行:28.76 毫秒
    平均:0.02876 微秒
    速度提升:253.13%

  3. tster 开启ActivCode.GetHashCode() 输出而不是ActivCode 的想法:
    1000000 次运行:34.69 毫秒
    平均:0.03469 微秒
    速度提升:185.04%

  4. 由包括 Auraseer、tster 和 kyoryu 在内的几位用户提出的想法是打开 ActivCode[0] 而不是 ActivCode:
    1000000 次运行:36.57 毫秒
    平均:0.03657 微秒
    速度提升:174.66%

  5. Loadmaster 使用快速哈希的想法,ActivCode[0] + ActivCode[1]*0x100:
    1000000 次运行:39.53 毫秒
    平均:0.03953 微秒
    速度提升:153.53%

  6. 许多人建议使用哈希表 (Dictionary<string, MarketDataExchange>):
    1000000 次运行:88.32 毫秒
    平均:0.08832 微秒
    速度提升:12.36%

  7. 使用二分查找:
    1000000 次运行:1031 毫秒
    平均:1.031 微秒
    速度提升:无(性能变差)

我只想说,看到人们对这个简单的问题有多少不同的想法,真是太酷了。这对我来说非常有趣,我非常感谢迄今为止做出贡献和提出建议的每个人。

【问题讨论】:

  • 重复 stackoverflow.com/questions/395618/if-else-vs-switch , stackoverflow.com/questions/94305/… , stackoverflow.com/questions/767821/… , stackoverflow.com/questions/1071856/… 等等,所有这些都谈到优化 switch 语句,使用 if/else 以及它如何编译到 IL
  • @Donnie,除了其中一个问题之外,所有问题都在询问是否使用 if/else 或 switch。最后一个是关于如何摆脱 switch 语句。所有这些问题都与这个问题完全不同,这是关于优化特定功能的问题。
  • @Dan,你发布你所做的……对吧?
  • @不退款不退货:你是对的。我可能不应该说,“这是我们应用程序中最慢的部分,请帮助我们优化它!”我应该说的是我发布这个只是为了好玩,这样人们就可以发布他们的想法并可能学到一些新的和有趣的东西。我也应该把fun这个词加粗,这样没人会错过它......
  • 哈哈,好一个丹。这是我参与过的最有趣的 SO 主题。

标签: c# optimization switch-statement


【解决方案1】:

假设每个输入都是有效的ActivCode,您可以更改枚举值并与GetHashCode 实现高度耦合:

enum MarketDataExchange
{
    NONE,
    NBBO = 371857150,
    AMEX = 372029405,
    BSE = 372029408,
    BATS = -1850320644,
    NSE = 372029407,
    CHX = -284236702,
    NYSE = 372029412,
    ARCA = -734575383,
    NASDAQ = 372029421,
    NASDAQ_ADF = -1137859911,
    CBOE = 372029419,
    PHLX = 372029430,
    DIRECTEDGE = 372029429
}

public static MarketDataExchange GetMarketDataExchange(string ActivCode)
{
    if (ActivCode == null) return MarketDataExchange.NONE;

    return (MarketDataExchange)ActivCode.GetHashCode();
}

【讨论】:

  • Eric Lippert 在此处警告使用字符串哈希:blogs.msdn.com/ericlippert/archive/2005/10/24/… - 这些值可能会在 CLR 的未来版本中发生变化。
  • @Jason:我知道这一点。事实上,若昂·安杰洛在这里提供的价值观对我不起作用。我必须首先从我的机器上的 CLR 版本中找出哈希码。每次升级 CLR 时都必须重新访问该功能,这将是一个维护难题;但由于这个问题是关于优化,而不是可维护性,所以我不得不向 João 致敬,因为它提出了非常快的东西。
  • 如果您担心 CLR 版本会绊倒您,您可以随时按照其他答案中的建议设计自己的哈希函数。
  • 我想知道您是否正在用一个领域的加速来换取另一个领域的减速。代码在使用这些枚举的地方会不会效率低下?
  • @Jason:完全同意,但正如 Dan 解释的那样,这只是一个有趣的优化挑战。
【解决方案2】:

我会推出自己的快速哈希函数并使用整数 switch 语句来避免字符串比较:

int  h = 0;  

// Compute fast hash: A[0] + A[1]*0x100
if (ActivCode.Length > 0)
    h += (int) ActivCode[0];
if (ActivCode.Length > 1)
    h += (int) ActivCode[1] << 8;  

// Find a match
switch (h)
{
    case 0x0000:  return MarketDataExchange.NBBO;        // ""
    case 0x0041:  return MarketDataExchange.AMEX;        // "A"
    case 0x0042:  return MarketDataExchange.BSE;         // "B"
    case 0x5442:  return MarketDataExchange.BATS;        // "BT"
    case 0x0043:  return MarketDataExchange.NSE;         // "C"
    case 0x574D:  return MarketDataExchange.CHX;         // "MW"
    case 0x004E:  return MarketDataExchange.NYSE;        // "N"
    case 0x4150:  return MarketDataExchange.ARCA;        // "PA"
    case 0x0051:  return MarketDataExchange.NASDAQ;      // "Q"
    case 0x4451:  return MarketDataExchange.NASDAQ_ADF;  // "QD"
    case 0x0057:  return MarketDataExchange.CBOE;        // "W"
    case 0x0058:  return MarketDataExchange.PHLX;        // "X"
    case 0x0059:  return MarketDataExchange.DIRECTEDGE;  // "Y"
    default:      return MarketDataExchange.NONE;
}

我的测试表明,这比原始代码快 4.5 倍

如果 C# 有预处理器,我会使用宏来形成 case 常量。

这种技术比使用哈希表更快,当然也比使用字符串比较更快。它适用于最多 4 个 32 位整数的字符串,以及最多 8 个使用 64 位长整数的字符。

【讨论】:

  • 现在用跳转表替换switch,它应该会从中挤出一点点!
  • 非常好。我采用了一种非常相似的技术和一个简单的校验和,并获得了相似的性能。但你的更好。
  • 我确实通过调整 if 语句实现了 8% 的改进... int h; if (ActivCode.Length == 1) h = (int)ActivCode[0];否则如果 (ActivCode.Length == 2) h = (int)ActivCode[1]
  • 我无法验证 4.5 倍速度提升的说法。相反,我只得到了 3 倍的改进,实际上与我的答案相同。我对每个案例调用参考方法 50,000,000 次时的结果需要 1 分 30 秒;此方法耗时 27.7 秒;我的方法耗时 28.5 秒。
  • 这个很聪明,肯定是对switch的改进。但是,这并不是迄今为止发布的最快的解决方案。我将在今天晚些时候发布一些测试结果...
【解决方案3】:

如果您知道各种代码出现的频率,那么更常见的代码应该排在列表顶部,这样就可以减少比较。但是让我们假设你没有那个。

假设 ActivCode 始终有效当然会加快速度。您不需要测试 null 或空字符串,您可以从 switch 的末尾省略一个测试。也就是说,测试除 Y 之外的所有内容,如果没有找到匹配项,则返回 DIRECTEDGE。

不要打开整个字符串,而是打开它的第一个字母。对于有更多字母的代码,请在开关盒内进行第二次测试。像这样的:

switch(ActivCode[0])
{
   //etc.
   case 'B':
      if ( ActivCode.Length == 1 ) return MarketDataExchange.BSE; 
      else return MarketDataExchange.BATS;
      // etc.
}

如果您可以返回并更改代码以使它们都是单个字符,那就更好了,因为您将永远不需要超过一个测试。更好的是使用枚举的数值,这样您就可以简单地进行转换,而不必首先切换/翻译。

【讨论】:

  • +1 表示实际上提出了可以提高性能的建议!你是对的,假设有效数据就不需要if (ActivCode == null)。但是,请注意空字符串实际上是有效代码 (MarketDataExchange.NBBO)。无论如何,这实际上非常接近我们提出的最佳选择......
  • 这会在空字符串大小写上引发异常。
  • 是的,我没有注意到空字符串是有效值。对此的明显解决方法是按另一个顺序进行测试——首先按长度 switch(),然后在每个非零长度的情况下,按第一个字符进行 switch()。
【解决方案4】:

我会为键值对使用字典并利用 O(1) 查找时间。

【讨论】:

  • 如果您根据上面的 switch 语句对此进行分析,您会发现基本上是均匀的结果(至少使用最新的 C# 编译器和 Microsoft 的 CLR)。
  • 嘿,正如我不久前在其他地方读到的:O(1) 中的 1 可能是一个高于 O(n) 总和的值,具体取决于您的 n。跨度>
  • C# 本身将为具有足够数量分支的字符串切换生成基于哈希表的实现。
【解决方案5】:

你有关于哪些字符串更常见的统计数据吗?这样可以先检查这些吗?

【讨论】:

  • 这绝对是个好主意,尽管我相信switch 这种长度的声明实际上并不重要。如果函数使用 if/else 块代替,它会。我可能是错的。但无论如何,我相信在我们的案例中,所有字符串都会或多或少同样普遍。
  • +1 这是我的第一个想法。 @Dan 你确定这些字符串都是同样可能的。我不知道你的项目,但我认为纽约证券交易所和纳斯达克的搜索量将比 ARCA(谷歌搜索时返回阿尔伯塔屋顶承包商协会)更多。
  • @Larry,说实话,不,完全不确定。事实上,最常见的代码很可能是空字符串,它代表一个 NBBO 刻度。但这似乎是一个有争议的问题,因为我们的测试表明switch 语句的顺序实际上并不重要(这与编译器将其转换为跳转表是一致的)。
【解决方案6】:

有一个有效的输入可以使用

if (ActivCode.Length == 0)
    return MarketDataExchange.NBBO;

if (ActivCode.Length == 1)
    return (MarketDataExchange) (ActivCode[0]);

return (MarketDataExchange) (ActivCode[0] | ActivCode[1] << 8);

【讨论】:

  • 假设您可以更改原始枚举的值,我认为这可能是迄今为止最快的解决方案。
【解决方案7】:

更改开关以打开字符串的 HashCode()。

【讨论】:

  • 一个完整的'另一个函数调用具有几个整数算术函数会比平均 5 个字符的比较快吗?
  • 视情况而定。字符比较是否由编译器内联?我的意思是我们在这里真正讨论的是编译器特定的微优化。
  • 我对这个建议非常怀疑,但在我的机器上试用它实际上比直接switch 缩短了一半时间。这一简单的改变甚至可以与我们提出的最快的解决方案相媲美(尽管不完全匹配)。 +1。
  • 但是,在给定两个接收相同哈希码的值的情况下,此解决方案总是有可能会中断
  • 没错,但是对于这么小的一组,我认为我们是安全的。
【解决方案8】:

假设代码生成器创建了一个查找表,或者 - 失败 - 自己构建查找表,我将 tster 的回复推断为“切换自定义哈希函数”。

自定义哈希函数应该很简单,例如:

(int)ActivCode[0]*2 + ActivCode.Length-1

这需要一个包含 51 个元素的表,并且很容易保存在 L1 缓存中,假设如下:

  • 输入数据必须已经过验证
  • 空字符串必须单独处理
  • 没有两个字符的代码以相同的字符开头
  • 添加新案例很难

如果您可以使用对ActivCode[0] 的不安全访问产生“\0”终止符,则可以合并空字符串大小写。

【讨论】:

    【解决方案9】:

    如果我在这里有什么问题,请原谅我,我是根据我对 C++ 的了解来推断的。例如,如果你取一个空字符串的 ActivCode[0],在 C++ 中你会得到一个值为零的字符。

    创建一个你初始化一次的二维数组;第一个维度是代码的长度,第二个维度是字符值。填充您要返回的枚举值。现在你的整个函数变成了:

    public static MarketDataExchange GetMarketDataExchange(string ActivCode) {
        return LookupTable[ActivCode.Length][ActivCode[0]];
    }
    

    幸运的是,与其他两字符代码相比,所有两字符代码的首字母都是唯一的。

    【讨论】:

    • 当然这只适用于有效输入,因为它只检查两个字符之一。
    • +1 提出了与我和我的同事相同的解决方案——在我看来,这是一个很好的解决方案;)我想伟大的思想是一样的。
    • 我可能应该指出,我们的方法有一个不同之处:尽管在 C++ 中您可能会检查ActivCode[0] 并得到 0,但在 C# 中会抛出异常。所以我们必须先添加if (ActivCode.Length &lt; 1) return MarketDataExchange.NBBO;
    【解决方案10】:

    我会将其放入字典中,而不是使用 switch 语句。话虽如此,它可能没有什么不同。或者它可能。见C# switch statement limitations - why?

    【讨论】:

    • 我认为,如果您对其进行测量,您会发现这不会产生显着的性能提升。
    • 它可能不会有任何区别,因为 C# 编译器可以(并且确实)它自己,如果有足够的案例值得。
    【解决方案11】:
    1. 避免所有字符串比较。
    2. 避免查看多个字符(永远)
    3. 避免使用 if-else,因为我希望编译器能够尽可能优化
    4. 尝试在单次开关跳转中得到结果

    代码:

    public static MarketDataExchange GetMarketDataExchange(string ActivCode) {
        if (ActivCode == null) return MarketDataExchange.NONE;
        int length = ActivCode.Length;
        if (length == 0) return MarketDataExchange.NBBO;
    
        switch (ActivCode[0]) {
            case 'A': return MarketDataExchange.AMEX;
            case 'B': return (length == 2) ? MarketDataExchange.BATS : MarketDataExchange.BSE;
            case 'C': return MarketDataExchange.NSE;
            case 'M': return MarketDataExchange.CHX;
            case 'N': return MarketDataExchange.NYSE;
            case 'P': return MarketDataExchange.ARCA;
            case 'Q': return (length == 2) ? MarketDataExchange.NASDAQ_ADF : MarketDataExchange.NASDAQ;
            case 'W': return MarketDataExchange.CBOE;
            case 'X': return MarketDataExchange.PHLX;
            case 'Y': return MarketDataExchange.DIRECTEDGE;
            default:  return MarketDataExchange.NONE;
        }
    }
    

    【讨论】:

    • 可能有一种方法可以做一个算术技巧来避免 B 和 Q 情况下的分支。例如,switch(ActiveCode[0] + length + length) 然后打开您计算的任何内容。问题是避免开关条件冲突。
    • switch (ActiveCode[0] / length) 为您提供相同的开关,除了长度 == 2 的 B 和 Q 情况分别为您提供 33 和 40。不知道是否值得分开。
    【解决方案12】:

    通过预先填充索引表以利用简单的指针算法来换取内存。

    public class Service 
    {
        public static MarketDataExchange GetMarketDataExchange(string ActivCode) {
        {
            int x = 65, y = 65;
            switch(ActivCode.Length)
            {
                case 1:
                    x = ActivCode[0];
                    break;
                case 2:
                    x = ActivCode[0];
                    y = ActivCode[1];
                    break;
            }
            return _table[x, y];
        }
    
        static Service()
        {
            InitTable();
        }
    
        public static MarketDataExchange[,] _table = 
            new MarketDataExchange['Z','Z'];
    
        public static void InitTable()
        {
            for (int x = 0; x < 'Z'; x++)
                for (int y = 0; y < 'Z'; y++)
                    _table[x, y] = MarketDataExchange.NONE;
    
            SetCell("", MarketDataExchange.NBBO);
            SetCell("A", MarketDataExchange.AMEX);
            SetCell("B", MarketDataExchange.BSE);
            SetCell("BT", MarketDataExchange.BATS);
            SetCell("C", MarketDataExchange.NSE);
            SetCell("MW", MarketDataExchange.CHX);
            SetCell("N", MarketDataExchange.NYSE);
            SetCell("PA", MarketDataExchange.ARCA);
            SetCell("Q", MarketDataExchange.NASDAQ);
            SetCell("QD", MarketDataExchange.NASDAQ_ADF);
            SetCell("W", MarketDataExchange.CBOE);
            SetCell("X", MarketDataExchange.PHLX);
            SetCell("Y", MarketDataExchange.DIRECTEDGE);
        }
    
        private static void SetCell(string s, MarketDataExchange exchange)
        {
            char x = 'A', y = 'A';
            switch(s.Length)
            {
                case 1:
                    x = s[0];
                    break;
                case 2:
                    x = s[0];
                    y = s[1];
                    break;
            }
            _table[x, y] = exchange;
        }
    }
    

    使枚举基于字节以节省一点空间。

    public enum MarketDataExchange : byte
    {
        NBBO, AMEX, BSE, BATS, NSE, CHX, NYSE, ARCA, 
        NASDAQ, NASDAQ_ADF, CBOE, PHLIX, DIRECTEDGE, NONE
    }
    

    【讨论】:

    • 是的,非常好。与我自己(和 Mark Ransom)的想法几乎相同,但做了一些深思熟虑的修改。
    【解决方案13】:

    如果枚举值是任意的,您可以这样做...

    public static MarketDataExchange GetValue(string input)
    {
        switch (input.Length)
        {
            case 0: return MarketDataExchange.NBBO;
            case 1: return (MarketDataExchange)input[0];
            case 2: return (MarketDataExchange)(input[0] << 8 | input[1]);
            default: return MarketDataExchange.None;
        }
    }
    

    ...如果您想完全发疯,您还可以使用带有指针的不安全调用,如Pavel Minaev ... 上面的纯演员版本比这个不安全的版本快。

    unsafe static MarketDataExchange GetValue(string input)
    {
        if (input.Length == 1)
            return (MarketDataExchange)(input[0]);
        fixed (char* buffer = input)
            return (MarketDataExchange)(buffer[0] << 8 | buffer[1]);
    }
    

    public enum MarketDataExchange
    {
        NBBO = 0x00, //
        AMEX = 0x41, //A
        BSE = 0x42, //B
        BATS = 0x4254, //BT
        NSE = 0x43, //C
        CHX = 0x4D57, //MW
        NYSE = 0x4E, //N
        ARCA = 0x5041, //PA
        NASDAQ = 0x51, //Q
        NASDAQ_ADF = 0x5144, //QD
        CBOE = 0x57, //W
        PHLX = 0x58, //X
        DIRECTEDGE = 0x59, //Y
    
        None = -1
    }
    

    【讨论】:

    • 如果允许使用不安全代码,您可以进一步优化:获取指向字符串数据的指针并直接返回 - fixed (MarketDataExchange* p = (MarketDataExchange*)(char*)input) { return *p; }。无需额外检查即可对 1 字符和 2 字符字符串执行此操作,因为 1 字符字符串将在数据之后立即以字符串表示形式具有\0(即00 00),并且可以通过指针访问。所以只需要检查Length==0Length==2,如果我们假设输入有效,那么只有Length==0
    • 使用fixed 的目的不是为了避免索引检查,而是为了避免移位运算符(我不确定 JIT 是否可以优化它们)。但是,您为回答所做的编辑将完全避免索引检查 - 您确实需要将指针投射到那里才能充分利用它。
    • 您的样本无法为我编译。无论哪种方式,转变都应该非常快。这样做还有很多其他问题,例如当您传入无效值(例如“Z”甚至“a”)时会发生什么
    • 我只是在没有移位的情况下再次尝试,并且字符串被 .net 存储为 UTF-16 的事实也会让我更改枚举值。但这已经远远超过了疯狂的程度。
    • unsafe 版本比 shift/cast 版本慢。如果有兴趣,我会将结果发布在我的博客上。
    【解决方案14】:

    +1 用于使用字典。不一定是为了优化,但它会更干净。

    我可能也会对字符串使用常量,尽管我怀疑这会给你带来任何性能方面的好处。

    【讨论】:

      【解决方案15】:

      混乱,但使用嵌套 if 和硬编码的组合可能会击败优化器:-

         if (ActivCode < "N") {
               // "" to "MW"
               if (ActiveCode < "BT") {
                  // "" to "B"
                  if (ActiveCode < "B") {
                      // "" or "A"
                      if (ActiveCode < "A") {
                            // must be ""
                           retrun MarketDataExchange.NBBO;
                      } else {
                           // must be "A"
                          return MarketDataExchange.AMEX;
                      }
                  } else {
                      // must be "B"
                      return MarketDataExchange.BSE;
                  }
               } else {
                  // "BT" to "MW"
                  if (ActiveCode < "MW") {
                      // "BT" or "C"
                      if (ActiveCode < "C") {
                            // must be "BT"
                           retrun MarketDataExchange.NBBO;
                      } else {
                           // must be "C"
                          return MarketDataExchange.NSE;
                      }
                  } else {
                  // must be "MV"
                      return MarketDataExchange.CHX;
                  }
               }
          } else {
              // "N" TO "Y"
               if (ActiveCode < "QD") {
                  // "N" to "Q"
                  if (ActiveCode < "Q") {
                      // "N" or "PA"
                      if (ActiveCode < "PA") {
                            // must be "N"
                           retrun MarketDataExchange.NYSE;
                      } else {
                           // must be "PA"
                          return MarketDataExchange.ARCA;
                      }
                  } else {
                      // must be "Q"
                      return MarketDataExchange.NASDAQ;
                  }
               } else {
                  // "QD" to "Y"
                  if (ActiveCode < "X") {
                      // "QD" or "W"
                      if (ActiveCode < "W") {
                            // must be "QD"
                           retrun MarketDataExchange.NASDAQ_ADF;
                      } else {
                           // must be "W"
                          return MarketDataExchange.CBOE;
                      }
                  } else {
                  // "X" or "Y"
                      if (ActiveCode < "Y") {
                            // must be "X"
                           retrun MarketDataExchange.PHLX;
                      } else {
                           // must be "Y"
                          return MarketDataExchange.DIRECTEDGE;
                      }
                  }
               }
          }
      

      这会通过三到四次比较获得正确的功能。除非您的代码预计每秒运行几次,否则我什至不会真正考虑这样做!

      您进一步对其进行了优化,以便仅发生单个字符比较。 例如用 '>= "B" ' 替换 '

      【讨论】:

      • 每秒几次?每秒尝试数千次 ;) 信不信由你,我们实际上确实尝试了二进制搜索......结果是一个开关明显更快,可能是因为它只需要一个(在哈希上,因为编译器为开关生成一个跳转表)。
      • 我猜是要神经兮兮的。看起来像是跳跃预测的噩梦(尽管自从我上次涉足它以来情况确实有所改善)。不过,它允许合并概率优化。
      【解决方案16】:

      您所有的字符串最多只有 2 个字符长和 ASCII,所以我们可以使用每个字符 1 个字节。 此外,更有可能的是,它们也永远不会有\0 出现在其中(.NET string 允许嵌入空字符,但许多其他东西不允许)。有了这个假设,我们可以将所有字符串空填充为每个正好 2 个字节,或 ushort:

      ""   -> (byte) 0 , (byte) 0   -> (ushort)0x0000
      "A"  -> (byte)'A', (byte) 0   -> (ushort)0x0041
      "B"  -> (byte)'B', (byte) 0   -> (ushort)0x0042
      "BT" -> (byte)'B', (byte)'T'  -> (ushort)0x5442
      

      现在我们在相对 (64K) 较短的范围内有一个整数,我们可以使用查找表:

      MarketDataExchange[] lookup = {
          MarketDataExchange.NBBO, 
          MarketDataExchange.NONE, 
          MarketDataExchange.NONE, 
          ...
          /* at index 0x041 */
          MarketDataExchange.AMEX,
          MarketDataExchange.BSE,
          MarketDataExchange.NSE,
          ...
      };
      

      现在,获取给定字符串的值是:

      public static unsafe MarketDataExchange GetMarketDataExchange(string s)
      {
         // Assume valid input
         if (s.Length == 0) return MarketDataExchange.NBBO;
      
         // .NET strings always have '\0' after end of data - abuse that
         // to avoid extra checks for 1-char strings. Skip index checks as well.
         ushort hash;
         fixed (char* data = s)
         {
             hash = (ushort)data[0] | ((ushort)data[1] << 8);
         }
      
         return lookup[hash];
      }
      

      【讨论】:

        【解决方案17】:

        将案例放在具有非线性访问的排序结构中(如哈希表)。 您拥有的开关将具有线性时间。

        【讨论】:

          【解决方案18】:

          您可以通过根据最常用的代码排序代码来获得适度的加速。

          但我同意 Cletus 的观点:我能想到的最佳加速方法是使用具有足够空间的哈希映射(这样就不会发生冲突)。

          【讨论】:

          • 无论如何,MS C# 编译器都会将此长度的 switch 语句转换为跳转表;因此,哈希映射实际上并没有提供显着的改进(至少根据我们的测量)。
          【解决方案19】:

          一些随机的想法,可能并不都适用:

          切换字符串中的第一个字符,而不是字符串本身,并对可以包含多个字母的字符串进行子切换?

          哈希表肯定会保证 O(1) 的检索,但对于较少的比较可能不会更快。

          不要使用字符串,而是使用枚举或享元之类的东西。在这种情况下使用字符串似乎有点脆弱......

          如果你真的需要它尽可能快,你为什么不把它写成汇编呢? :)

          【讨论】:

          • 字符串是我们从供应商那里得到的。否则我同意你的观点,它不是一种理想的数据形式。 (请注意,该函数实际上将字符串 转换为 枚举——在应用程序的其他任何地方都使用。)这不是在汇编中,因为,好吧,基本上,我们毕竟只是人类......我们的应用程序具有大量的功能,并且对新功能的需求在时间限制方面非常极端。
          • 公平的。程序集注释是半snark,好像你真的需要优化代码,你是否应该在托管代码中这样做是一个有效的问题......无论如何,根据附加信息,我仍然会看在哈希表中,或直接比较适当索引处的字符,而不是进行完整的字符串比较。无论哪种方式,在其周围放置一个分析器块以实际确定速度可能是第一步。
          • 好点将字符串转换为 smallint 将适用于大多数提供的值,例如。 “QD” -> x'5144' -> 20844 ,“Q” -> x'5100' -> 20736 等(假设 ASCI 和 Null 终止的字符串)。空字符串会引起一些问题,因为它会是 '00' 后面跟着一些随机的东西,但你可以检查
          • 您可以只检查第一个字符,这对于任何一个字符串或空字符串都足够了。在可能是双字符串的情况下,可能是多个两个字符串,也可能是一个或两个字符串,只然后检查第二个字符。
          • 你不能假设 ASCII 因为 .NET 字符串总是 Unicode,所以每个字符两个字节。从好的方面来说,这也意味着空终止符是00 00
          【解决方案20】:

          我们可以将 ActivCode 转换为 int,然后在我们的案例语句中使用 int 吗?

          【讨论】:

          • C# 编译器不允许这样做。
          • 不是直接的,但您始终可以使用位移来获得相同的结果。或者不安全的代码。
          【解决方案21】:

          使用代码的长度从该代码创建唯一值,而不是使用 GetHashCode()。事实证明,如果您使用按代码长度移动的代码的第一个字母,则不会发生冲突。这将成本降低到两次比较,一次数组索引和一次移位(平均)。

          public static MarketDataExchange GetMarketDataExchange(string ActivCode)
          {
              if (ActivCode == null)
                  return MarketDataExchange.NONE;
              if (ActivCode.Length == 0)
                  return MarketDataExchange.NBBO;
              return (MarketDataExchange)((ActivCode[0] << ActivCode.Length));
          }
          
          public enum MarketDataExchange
          {
              NONE = 0,
              NBBO = 1,
              AMEX = ('A'<<1),
              BSE = ('B'<<1),
              BATS = ('B'<<2),
              NSE = ('C'<<1),
              CHX = ('M'<<2),
              NYSE = ('N'<<1),
              ARCA = ('P'<<2),
              NASDAQ = ('Q'<<1),
              NASDAQ_ADF = ('Q'<<2),
              CBOE = ('W'<<1),
              PHLX = ('X'<<1),
              DIRECTEDGE = ('Y'<<1),
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-10-01
            • 2013-05-18
            • 1970-01-01
            • 2021-12-14
            • 1970-01-01
            • 2011-11-17
            相关资源
            最近更新 更多