你会如何尽可能快地制作这个切换声明?

时间:2009-12-03 04:02:58

标签: c# optimization switch-statement

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上发布 fun ,这也不仅仅是浪费时间。

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


结果

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

由于我运行了这些测试,其他一些用户甚至发布了更新的想法...我将在适当的时候测试它们,当我还有几分钟的时间。

我在两台不同的机器上运行这些测试:家用个人计算机(运行Windows 7 64位的双核Athlon和4 Gb RAM)和我的开发机器(带有2 Gb RAM的双核Athlon)运行Windows XP SP3)。显然,时代不同;但是,相对次,意味着每种方法与其他方法的比较方式是相同的。也就是说,最快的是两台机器上最快的等等。

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

但首先,作为参考 - 原始的开关声明:
1000000次运行:98.88 ms
平均值:0.09888微秒

到目前为止最快的优化:

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

  2. 我的同事和我将ActivCode[0]投射到int并从启动时初始化的数组中检索相应MarketDataExchange的想法(Mark Ransom建议这个完全相同的想法) ):
    1000000次运行:28.76 ms
    平均值:0.02876微秒
    提速:253.13%

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

  4. 包括Auraseer,tster和kyoryu在内的多个用户建议切换ActivCode[0]而不是ActivCode的想法:
    1000000次运行:36.57 ms
    平均值:0.03657微秒
    提速:174.66%

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

  6. 使用散列表(Dictionary<string, MarketDataExchange>),如许多人所建议的那样:
    1000000次运行:88.32 ms
    平均值:0.08832微秒
    提速:12.36%

  7. 使用二分搜索:
    1000000次运行:1031 ms
    平均值:1.031微秒
    速度提升:无(性能恶化)

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

21 个答案:

答案 0 :(得分:26)

假设每个输入都是有效的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();
}

答案 1 :(得分:21)

我将使用自己的快速哈希函数并使用整数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常量。

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

答案 2 :(得分:8)

如果您知道各种代码显示的频率,那么更常见的代码应该位于列表的顶部,因此进行的比较较少。但是我们假设你没有那个。

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

不要打开整个字符串,而是打开它的第一个字母。对于包含更多字母的代码,在开关盒内放置第二个测试。像这样:

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

如果您可以返回并更改代码以便它们都是单个字符会更好,因为您将永远不需要多个测试。更好的是使用枚举的数值,所以你可以简单地转换而不必首先切换/翻译。

答案 3 :(得分:6)

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

答案 4 :(得分:5)

您是否有关于哪些字符串更常见的统计信息?那么可以先检查一下吗?

答案 5 :(得分:5)

使用有效输入可以使用

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

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

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

答案 6 :(得分:4)

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

答案 7 :(得分:4)

如果我在这里弄错了,请原谅我,我从我对C ++的了解中推断出来。例如,如果你取一个空字符串的ActivCode [0],在C ++中你会得到一个值为零的字符。

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

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

幸运的是,与其他双字符代码相比,所有双字符代码在第一个字母中都是唯一的。

答案 8 :(得分:4)

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

自定义散列函数应该很简单,例如:

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

这需要一个包含51个元素的表,可以在以下假设下轻松保存在L1缓存中:

  • 输入数据必须已经过验证
  • 空字符串必须以sepsarately处理
  • 没有双字符代码以相同的字符开头
  • 添加新案例 hard

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

答案 9 :(得分:3)

我会把它放在字典中而不是使用switch语句。话虽如此,它可能没有什么区别。或者它可能。请参阅C# switch statement limitations - why?

答案 10 :(得分:3)

  1. 避免所有字符串比较。
  2. 避免查看多个角色(永远)
  3. 避免使用if-else,因为我希望编译器能够最好地优化它
  4. 尝试在单个切换跳转中获得结果
  5. 代码:

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

答案 11 :(得分:3)

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

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
}

答案 12 :(得分:2)

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

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
}

答案 13 :(得分:1)

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

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

通过三次或四次比较获得正确的功能。我甚至不会想到这样做是真的,除非你的代码预计会每秒运行几次!

您进一步确定它,以便只发生单个字符比较。 例如替换'&lt; “BT”'带'&gt; =“B”' - 速度更快,甚至更低!

答案 14 :(得分:1)

+1使用字典。不一定是优化,但它更清洁。

我也可能会使用常量来表示字符串,但我怀疑这会给你带来任何明智的表现。

答案 15 :(得分:1)

你的所有字符串最多都是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];
}

答案 16 :(得分:0)

我们可以将ActivCode强制转换为int,然后在case语句中使用int吗?

答案 17 :(得分:0)

一些随机的想法,可能并非全部适用:

打开字符串中的第一个字符,而不是字符串本身,并为可包含多个字母的字符串执行子开关?

哈希表当然可以保证O(1)检索,但对于较小数量的比较可能不会更快。

请勿使用字符串,使用枚举或类似flyweight的内容。在这种情况下使用字符串似乎有点脆弱......

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

答案 18 :(得分:0)

通过根据最常用的代码订购代码,您可以获得温和的加速。

但我同意Cletus:我能想到的最好的加速是使用具有足够空间的哈希图(以便没有碰撞。)

答案 19 :(得分:0)

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

答案 20 :(得分:0)

使用代码的长度从该代码创建唯一值,而不是使用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),
}