What is quicker, switch on string or elseif on type?
假设我可以选择识别代码路径,以便根据字符串比较或其他类型进行区分:
哪个更快,为什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
更新:我之所以问这个问题,主要是因为switch语句是perculiar,它描述了什么才算是一个案例。例如,它不允许您使用变量,只允许将常量移动到主程序集。我认为这是因为它所做的一些奇怪的事情。如果它只是翻译成elseifs(正如一个海报所评论的那样),那么为什么在case语句中不允许变量?
注意:我是后优化。在应用程序的慢速部分多次调用此方法。
Greg的配置文件结果对于他所涉及的具体场景非常有用,但有趣的是,考虑到许多不同的因素,包括要比较的类型的数量、相对频率以及基础数据中的任何模式,不同方法的相对成本都会发生显著变化。
简单的答案是,没有人能告诉你在你的特定场景中会有什么样的性能差异,你需要在你自己的系统中以不同的方式来测量性能,以得到一个准确的答案。
if/else链对于少量的类型比较是一种有效的方法,或者如果您能够可靠地预测哪些类型将构成您所看到的大多数类型。这种方法的潜在问题是,随着类型数量的增加,必须执行的比较数量也会增加。
如果我执行以下操作:
1 2 3 4 5 6 | int value = 25124; if(value == 0) ... else if (value == 1) ... else if (value == 2) ... ... else if (value == 25124) ... |
在输入正确的块之前,必须评估前面的每个if条件。另一方面
1 2 3 4 5 6 7 | switch(value) { case 0:...break; case 1:...break; case 2:...break; ... case 25124:...break; } |
将执行一个简单的跳转到正确的代码位。
在您的示例中,它变得更复杂的地方是,您的另一个方法使用了字符串上的开关,而不是更复杂的整数。在较低的级别上,字符串不能以整数值的方式打开,因此C编译器会为您提供一些帮助。
如果switch语句"足够小"(编译器自动执行其认为最好的操作),则打开字符串将生成与if/else链相同的代码。
1 2 3 4 5 | switch(someString) { case"Foo": DoFoo(); break; case"Bar": DoBar(); break; default: DoOther; break; } |
相同:
1 2 3 4 5 6 7 | if(someString =="Foo") { DoFoo(); } else if(someString =="Bar") { DoBar(); } else { DoOther(); } |
一旦字典中的项目列表"足够大",编译器将自动创建一个内部字典,该字典将从开关中的字符串映射到整数索引,然后根据该索引创建一个开关。
它看起来是这样的(想象一下,输入的条目比我输入的要多)
静态字段是在"隐藏"位置中定义的,该位置与包含
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //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种类型下,开关的速度比50种类型快一小部分(16%),开关的速度是原来的两倍多。
如果要打开大量类型,我建议使用第三种方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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... } } |
这与TedElliot建议的类似,但是使用运行时类型句柄而不是完整类型对象可以避免通过反射加载类型对象的开销。
以下是我的机器上的一些快速计时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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链中首先检查的类型组成,则该方法更快:
1 2 3 4 5 6 | 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链中的最后一件事,那么它具有相反的效果:
1 2 3 4 5 6 | 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检查最常见的几种类型,如果这些类型失败,则返回字典驱动的方法。
我刚刚实现了一个快速测试应用程序,并用Ants4对其进行了分析。规格:32位Windows XP中的.NET 3.5 SP1,代码内置于发布模式。
300万次试验:
- 开关:1.842秒
- 如果:0.344秒。
此外,switch语句的结果(毫不奇怪)显示,较长的名称需要更长的时间。
100万试验
- 鲍勃:0.612秒。
- 吉尔:0.835秒。
- 马尔科:1.093秒。
我看起来"如果其他"更快,至少是我创建的场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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";}} |
首先,你在比较苹果和桔子。您首先需要比较"打开类型"和"打开字符串",然后比较"打开类型"和"打开字符串",然后比较获胜者。
其次,这是OO设计的目的。在支持OO的语言中,打开类型(任何类型)是一种代码味道,它指向糟糕的设计。解决方案是使用抽象或虚拟方法(或类似的构造,取决于您的语言)从公共基派生。
如。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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()。
switch语句的执行速度比if-else if梯形图快。这是由于编译器能够优化switch语句。对于if-else if阶梯,代码必须按照程序员确定的顺序处理每个if语句。但是,由于switch语句中的每种情况都不依赖于前面的情况,因此编译器能够以提供最快执行速度的方式重新排序测试。
如果你已经完成了课程,我建议使用策略设计模式,而不是switch或elseif。
尝试对每个对象使用枚举,可以快速轻松地打开枚举。
如果要打开的类型是基元.NET类型,则可以使用type.gettypecode(类型),但如果它们是自定义类型,则它们都将作为typecode.object返回。
带有委托或处理程序类的字典也可以工作。
1 2 3 4 5 6 7 8 9 10 11 | 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 } |
除非你已经写了这个并且发现你有一个性能问题,否则我不会担心哪个更快。选择一个可读性更强的。记住,"过早的优化是万恶之源。"—唐纳德·克努斯
switch构造最初是针对整数数据的;它的目的是将参数直接用作"调度表"(指针表)的索引。因此,将有一个测试,然后直接启动到相关代码,而不是一系列测试。
这里的困难在于,它的使用已经被概括为"字符串"类型,显然不能用作索引,而且开关结构的所有优点都丧失了。
如果速度是您的目标,那么问题不在于您的代码,而在于您的数据结构。如果"名称"空间和您所显示的一样简单,那么最好将其编码为整数值(例如,创建数据时),并在"应用程序缓慢部分多次"中使用该整数。
switch()将编译为相当于一组elseif的代码。字符串比较将比类型比较慢得多。
我认为这里的主要性能问题是,在交换块中,比较字符串,在if-else块中,检查类型…这两个不一样,因此,我想说你是在"把土豆比作香蕉"。
我先比较一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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") {} |
我记得在一些参考书中读到if/else分支比switch语句更快。然而,对Blackwasp的一些研究表明,switch语句实际上更快:http://www.blackwasp.co.uk/speedtestifelswitch.aspx
事实上,如果你在比较典型的3到10(或更多)语句,我严重怀疑使用其中一个语句是否会获得真正的性能提升。
正如Chris已经说过的,为了可读性:什么更快,打开字符串或Elseif打开类型?
我不确定使用多态性的正确设计速度有多快。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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语句的作用会更好。如果您的函数实际上与类型上的操作无关,那么您可以在每个类型上定义一个枚举。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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(); } } |
我认为这必须比这两种方法更快。如果纳秒对您很重要,您可能需要尝试抽象类路由。
我创建了一个小控制台来显示我的解决方案,只是为了突出速度差。我使用了一个不同的字符串哈希算法,因为证书版本在运行时会变慢,不太可能出现重复,如果是这样,我的switch语句就会失败(到现在为止从未发生过)。我唯一的哈希扩展方法包含在下面的代码中。
我将在任何时候接受29个周期,超过695个周期,特别是在使用关键代码时。
使用给定数据库中的一组字符串,您可以创建一个小应用程序来在给定文件中创建常量,供您在代码中使用。如果添加了值,则只需重新运行批处理,并由解决方案生成和提取常量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | 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(); } |
记住,探查器是你的朋友。任何猜测大多数时候都是浪费时间。顺便说一句,我在JetBrains的Dottrace分析器方面有很好的经验。
我只是在阅读这里的答案列表,想分享这个基准测试,它比较了
我喜欢的是,这篇文章不仅比较了单左结构(如
根据结果,在8/9个测试案例中,
因此,如果你在寻找速度
开关字符串基本上被编译成if-else if梯形图。尝试反编译一个简单的。在任何情况下,测试字符串的均等性都应该更便宜,因为它们是被拘留的,需要的只是一个参考检查。在可维护性方面做一些有意义的事情;如果您正在编译字符串,请执行字符串切换。如果根据类型选择,则类型梯更合适。
开关的一个问题是使用字符串,比如"bob",这将在编译后的代码中导致更多的循环和行。生成的IL必须声明一个字符串,将其设置为"bob",然后在比较中使用它。因此,考虑到这一点,if语句将运行得更快。
埃昂的例子不起作用,因为你不能打开类型。(不,我不知道为什么,但我们已经试过了,它不起作用。它与变量类型有关)
如果您想测试这一点,只需构建一个单独的应用程序,并构建两个简单的方法,执行上面编写的操作,并使用ildasm.exe之类的工具查看IL。您会注意到if语句方法的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
我做的有点不同,打开的字符串将是常量,因此可以在编译时预测值。
在您的例子中,我将使用散列值,这是一个int开关,您有2个选项,使用编译时常量或在运行时计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //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; } } |
GetUniqueHashCode的扩展方法可以如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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); } } |
此代码的源已在此处发布请注意,使用加密的速度很慢,您通常会在应用程序启动时预热支持的字符串,我这样做的目的是将它们保存在静态字段中,因为它们不会更改,也与实例无关。请注意,我设置了节点对象的标记值,我可以使用任何属性或添加一个属性,只要确保这些属性与实际文本同步即可。
我在低延迟系统上工作,所有代码都是一系列命令:value,command:value….
现在这个命令都被称为64位整数值,所以这样的切换可以节省一些CPU时间。
当然,switch-on字符串会编译成比类型比较慢(比用于switch/case的典型整数比较慢得多)的字符串比较(每种情况一个)?
字符串比较始终完全依赖于运行时环境(除非字符串是静态分配的,尽管相互比较的需要是有争议的)。但是,类型比较可以通过动态或静态绑定来完成,并且无论哪种方式,它都比比较字符串中的单个字符更有效。
三个想法:
1)如果您要根据对象的类型做一些不同的事情,那么将该行为转移到这些类中可能是有意义的。然后,您可以调用childnode.dosomething(),而不是switch或if else。
2)比较类型将比字符串比较快得多。
3)在if-else设计中,您可以利用重新排序测试的优势。如果"jill"对象占通过那里的对象的90%,则首先测试它们。
我可能遗漏了一些东西,但您不能在类型上而不是字符串上执行switch语句吗?也就是说,
1 2 3 4 5 6 7 8 9 | switch(childNode.Type) { case Bob: break; case Jill: break; case Marko: break; } |