什么是更快,打开string或elseif的types?
比方说,我可以select一个代码path,以string比较为基础,否则如果是这样的话:
哪个更快,为什么?
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语句对于什么来说是个奇怪的事情。 例如它不会允许你使用variables,只有常数被移动到主程序集。 我以为它有这个限制,由于一些时髦的东西,它正在做。 如果只是翻译成elseifs(就像一个海报发表的评论)那么为什么我们不允许在case语句中使用variables?
警告:我在后优化。 这个方法在应用程序的缓慢部分被调用了很多次。
格雷格的个人资料结果对于他所描述的确切情况非常有用,但有趣的是,考虑到一系列不同的因素,不同方法的相对成本发生了显着的变化,这些因素包括被比较的types数量,相对频率以及基础数据中的任何模式。
简单的答案是,没有人可以告诉你在你的具体情况下性能差异将会是什么,你将需要在你自己的系统中以不同的方式测量性能,以获得准确的答案。
If / Else链是less数types比较的有效方法,或者如果您可以可靠地预测哪些types将构成大部分types的比较。 这种方法的潜在问题是,随着types数量的增加,必须执行的比较次数也会增加。
如果我执行以下操作:
int value = 25124; if(value == 0) ... else if (value == 1) ... else if (value == 2) ... ... else if (value == 25124) ...
在input正确的程序段之前,必须对每个之前的条件进行评估。 另一方面
switch(value) { case 0:...break; case 1:...break; case 2:...break; ... case 25124:...break; }
将执行一个简单的跳转到正确的代码位。
在你的例子中,更复杂的地方在于你的另一个方法使用了一个string开关而不是整数,而这个整数变得复杂一点。 在低级别,string不能以与整数值相同的方式打开,所以C#编译器会做一些神奇的事情来使这个工作。
如果switch语句“足够小”(编译器按照自认为最好的方式进行),则切换string将生成与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(); }
一旦字典中的项目列表变得足够大,编译器将自动创build一个内部字典,该字典将交换机中的string映射到一个整数索引,然后根据该索引进行切换。
它看起来像这样(想象更多的条目比我打算打字)
静态字段在与包含types为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(); }
在刚刚运行的一些快速testing中,If / Else方法的速度是3种不同types(其中types是随机分布的)的3倍。 在25种types的交换机中,50种types的交换机速度较小(16%),交换机的速度提高了一倍以上。
如果你要打开大量的types,我会build议第三种方法:
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所build议的类似,但是使用运行时types句柄而不是全types对象避免了通过reflection来加载types对象的开销。
以下是我的机器上的一些快速时机:
用5,000,000个数据元素(mode = Random)和5个typestesting3次迭代 方法时间%最佳 如果/否则179.67 100.00 TypeHandleDictionary 321.33 178.85 TypeDictionary 377.67 210.20 开关492.67 274.21 用5,000,000个数据元素(mode = Random)和10个typestesting3次迭代 方法时间%最佳 如果/另外271.33 100.00 TypeHandleDictionary 312.00 114.99 TypeDictionary 374.33 137.96 开关490.33 180.71 用5,000,000个数据元素(mode = Random)和15个typestesting3次迭代 方法时间%最佳 TypeHandleDictionary 312.00 100.00 如果/另外369.00 118.27 TypeDictionary 371.67 119.12 开关491.67 157.59 使用5,000,000个数据元素(mode = Random)和20个typestesting3次迭代 方法时间%最佳 TypeHandleDictionary 335.33 100.00 TypeDictionary 373.00 111.23 If / Else 462.67 137.97 开关490.33 146.22 用5,000,000个数据元素(mode = Random)和25个typestesting3次迭代 方法时间%最佳 TypeHandleDictionary 319.33 100.00 TypeDictionary 371.00 116.18 开关483.00 151.25 If / Else 562.00 175.99 用5,000,000个数据元素(mode = Random)和50个typestesting3次迭代 方法时间%最佳 TypeHandleDictionary 319.67 100.00 TypeDictionary 376.67 117.83 开关453.33 141.81 If / Else 1,032.67 323.04
至less在我的机器上,当用作方法input的types的分布是随机的时,types句柄字典方法击败所有超过15种不同types的所有其他types。
另一方面,如果input完全由在if / else链中首先检查的types组成,那么该方法快得多:
使用5,000,000个数据元素(mode = UniformFirst)和50个typestesting3次迭代 方法时间%最佳 如果/否则39.00 100.00 TypeHandleDictionary 317.33 813.68 TypeDictionary 396.00 1,015.38 开关403.00 1,033.33
相反,如果input始终是if / else链中的最后一项,则会产生相反的效果:
用5,000,000个数据元素(mode = UniformLast)和50个typestesting3次迭代 方法时间%最佳 TypeHandleDictionary 317.67 100.00 开关354.33 111.54 TypeDictionary 377.67 118.89 If / Else 1,907.67 600.52
如果您可以对input做出一些假设,那么您可以从混合方法中获得最佳性能,在这种方法中,您执行if / else检查最常见的几种types,如果这些方法失败,则会回到字典驱动的方法。
我刚刚实施了一个快速testing应用程序,并用ANTS 4进行了分析。
规格:.net 3.5 sp1在32位Windows XP中,代码内置在发行模式。
300万testing:
- 开关:1.842秒
- 如果:0.344秒。
此外,switch语句结果显示(不出所料),较长的名称需要更长的时间。
100万个testing
- 鲍勃:0.612秒。
- 吉尔:0.835秒。
- Marko:1.093秒。
我看起来像“如果其他”更快,至less是我创造的场景。
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";}}
首先,你要比较苹果和橘子。 您首先需要比较开关types与开关string,然后如果在typesvs如果在string,然后比较优胜者。
其次,这是面向对象的devise。 在支持面向对象的语言中,开启types(任何types)都是一种指向糟糕devise的代码味道。 解决scheme是从一个抽象的或虚拟的方法(或类似的结构,取决于你的语言)
例如。
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语句中的每个case不依赖于以前的情况,因此编译器能够以提供最快执行的方式重新sortingtesting。
如果你已经有了类,我build议使用策略devise模式,而不是switch或elseif。
尝试使用枚举为每个对象,您可以快速轻松地打开枚举。
除非你已经写了这个,并发现你有一个性能问题,我不会担心哪个更快。 去一个更可读的。 请记住,“过早优化是万恶之源”。 Donald Knuth
SWITCH结构最初是用于整数数据的; 它的意图是直接使用参数作为索引到一个“调度表”,一个指针表。 因此,会有一个单一的testing,然后直接启动相关的代码,而不是一系列的testing。
这里的难点在于它的使用已经被推广到“string”types,这显然不能被用作索引,并且SWITCH结构的所有优点都失去了。
如果速度是你的目标,那么问题不在于你的代码,而在于你的数据结构。 如果“名称”空间与您所展示的一样简单,那么最好将其编码为一个整数值(例如,在创build数据时),并在“在应用程序缓慢部分中多次使用”这个整数。
如果您打开的types是原始.NETtypes,则可以使用Type.GetTypeCode(Type),但是如果它们是自定义types,则它们都将返回为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 }
switch()将会编译成相当于一组else if的代码。 string比较将比types比较慢得多。
我记得在几本参考书中读到if / else分支比switch语句更快。 然而,对Blackwasp的一些研究表明switch语句实际上更快: http : //www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx
实际上,如果你比较典型的3到10个(或者多个)陈述,我真的怀疑使用这个或那个真正的性能增益。
正如克里斯已经说过,去可读性: 什么是更快,打开string或elseif的types?
我认为这里的主要性能问题是,在switch块中,比较string,在if-else块中检查types…这两个不一样,所以我会说你把土豆和香蕉比较“。
我首先比较一下:
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") {}
我不确定多合适的devise可能会多快。
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语句会做得更好。 如果你的函数实际上并不是关于这个types的一个动作,那么你可以定义一个枚举types。
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(); } }
我认为这个问题比这两种方法都要快。 如果纳秒对您来说很重要,您可能要尝试抽象类路由。
string比较将始终完全依赖于运行时环境(除非string是静态分配的,尽pipe需要比较这些string是有争议的)。 然而,types比较可以通过dynamic或静态绑定来完成,无论哪种方式,对于运行时环境来说,比比较string中的单个字符更有效。
当然,String上的开关将会编译成一个string比较(每个案例一个),比一个types比较慢(而且比用于switch / case的典型整数比较要慢)。
我可能会错过一些东西,但是不能在types而不是String上做switch语句吗? 那是,
switch(childNode.Type) { case Bob: break; case Jill: break; case Marko: break; }
三个想法:
1)如果你打算根据对象的types做一些不同的事情,把这种行为转移到这些类中可能是有意义的。 然后,而不是开关或if-else,你只需要调用childNode.DoSomething()。
2)比较types将比string比较快得多。
3)在if-elsedevise中,您可能能够利用重新sorting的testing。 如果“Jill”物体构成了90%的物体,那么先testing它们。
你使用开关的一个问题是使用string,比如“Bob”,这会在编译后的代码中产生更多的循环和行数。 生成的IL将必须声明一个string,将其设置为“Bob”,然后在比较中使用它。 所以考虑到这一点你的IF语句将运行得更快。
PS。 永旺的例子不会工作,因为你不能打开types。 (不,我不知道为什么,但我们已经尝试过,它不起作用,它与types是可变的)
如果你想testing这个,只需构build一个单独的应用程序,然后构build两个简单的方法来完成上面所写的内容,并使用诸如Ildasm.exe之类的方法来查看IL。 您会注意到IF语句Method中的ILless得多。
Ildasm附带VisualStudio …
ILDASM页面 – http://msdn.microsoft.com/en-us/library/f7dy01k1(VS.80).aspx
ILDASM教程 – http://msdn.microsoft.com/zh-cn/library/aa309387(VS.71).aspx
请记住,分析器是你的朋友。 任何猜测都是浪费大部分时间。 顺便说一句,我有JetBrains的dotTrace分析器的一个很好的经验。
打开string基本上被编译成if-else-if梯形图。 尝试反编译一个简单的。 在任何情况下,testingstringequailty应该是便宜的,因为他们是interned和所有需要的是参考检查。 在可维护性方面做什么是有道理的; 如果您正在压缩string,请执行string切换。 如果您正在根据types进行select,则types梯子更合适。