tags:

views:

2129

answers:

20

Lets say I have the option of identifying a code path to take on the basis of a string comparison or else iffing the type:

Which is quicker and why?

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

Update: The main reason I ask this is because the switch statement is perculiar about what counts as a case. For example it wont allow you to use variables, only constants which get moved to the main assembly. I assumed it had this restriction due to some funky stuff it was doing. If it is only translating to elseifs (as one poster commented) then why are we not allowed variables in case statements?

Caveat: I am post-optimising. This method is called many times in a slow part of the app.

+1  A: 

The switch() will compile out to code equivalent to a set of else ifs. The string comparisons will be much slower than the type comparisons.

moonshadow
Does the CLR not perform any funky tricks within a switch statement? Why else does it force you to only use constants as case statements instead of variables if it's only translating to else ifs?
Quibblesome
The CLR will perform funky tricks if the switch statement is using basic types, however here each case requires a string comparison so there is little scope for optimisation.
moonshadow
C# does not compile string-based switches to string compares. Since the case labels have to be literals, it uses tricks such as interning the switched variable, switching on the hashcode then checking object identity (which will work due to the interning) to make sure the match was correct.
Michael Burr
oOo now that is very interesting. So the CLR makes an exception for strings that it doesn't make for other types? So does this mean it is equivalent to else ifs?
Quibblesome
+1  A: 

Unless you've already written this and find you have a performance problem I wouldn't worry about which is quicker. Go with the one that's more readable. Remember, "Premature optimization is the root of all evil." - Donald Knuth

Chris Upchurch
Except we're not prematurely optimising. We're post optimising. Sorry but -1.
Quibblesome
The answers here are not solely for the benefit of the questioner, they're for anyone who comes along later. People need to realize that making this decision up front for performance reasons is not the best way to do it.
Chris Upchurch
Of course I agree.
Nescio
Oh great, now the answer that states that the question is irrelevant is above all the other answers that actually try to answer the question.WAY TO GO GUYS! :P
Quibblesome
@Quarrelsome: cautioning against premature optimization is relevant to any optimization question unless you specify that you've already profiled your code and found it to be too slow. Put that in your question and my answer will probably stop being voted up.
Chris Upchurch
Jesus, so now every single time we talk performance we have to add the caveat "oh by the way i'm not prematurely optimising!"..... *sigh*.
Quibblesome
Given how prevalent premature optimization is, yes.
Chris Upchurch
I agree with Quarrelsome here. I think it's safe to assume anyone looking for an answer to this question actually wants to know which is faster. Saying "don't worry about it" is NOT a good answer.
Outlaw Programmer
And honestly, I think a lot of questions that talk about how people profiled their code and found a particular part was too slow will probably do more to get the point across than repeated invocations of Donald Knuth.
Chris Upchurch
@Outlaw:I agree they want to know which is faster, but a lot of people want to know for the wrong reason (because they are prematurely optimizing).
Chris Upchurch
A: 

String comparison will always rely completely on the runtime environment (unless the strings are statically allocated, though the need to compare those to each other is debatable). Type comparison, however, can be done through dynamic or static binding, and either way it's more efficient for the runtime environment than comparing individual characters in a string.

Magsol
A: 

Surely the switch on String would compile down to a String comparison (one per case) which is slower than a type comparison (and far slower than the typical integer compare that is used for switch/case)?

JeeBee
+1  A: 

I may be missing something, but couldn't you do a switch statement on the type instead of the String? That is,

switch(childNode.Type)
{
case Bob:
  break;
case Jill:
  break;
case Marko:
  break;
}
Aeon
No, the switch operation doesn't work on objects such as types. "Integral types" only
Quibblesome
A string isn't an integral type!
moonshadow
Then why does the Framework allow a string but not a type with the compiler error: "A value of an integral type expected".Is it just a little trick to allow you to use strings even though they are not integral types?
Quibblesome
Ohh. Ok, sorry :) I don't know c#, it would've seemed logical to allow things that are valid as an if condition, to be a switch condition.
Aeon
+11  A: 

Switch statement is faster to execute than the if-else-if ladder. This is due to the compiler's ability to optimise the switch statement. In the case of the if-else-if ladder, the code must process each if statement in the order determined by the programmer. However, because each case within a switch statement does not rely on earlier cases, the compiler is able to re-order the testing in such a way as to provide the fastest execution.

Nescio
However, the Type comparisons are much easier to maintain as well. -- Try not to prematurely optimize.
Nescio
This is helpful in saying that what's written isn't exactly what's executed, but misleading in suggesting that IFs can't be optimized away. I'm no optimizer expert, but I'm looking at a class in Reflector where an If/ElseIf with a readonly variable is implemented in IL the same as a SWITCH.
kcrumley
+4  A: 

If you've got the classes made, I'd suggest using a Strategy design pattern instead of switch or elseif.

Gary Kephart
That's an excellent suggestion! Let the object itself decide what it needs to do.
Bob King
That would be much more elegant and save all this faffing around. However to do this would require a great deal of refactoring in this area and would only be used as a last resort. But I do agree with you in general. :D
Quibblesome
+1  A: 

I recall reading in several reference books that the if/else branching is quicker than the switch statement. However, a bit of research on Blackwasp shows that the switch statement is actually faster: http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

In reality, if you're comparing the typical 3 to 10 (or so) statements, I seriously doubt there's any real performance gain using one or the other.

As Chris has already said, go for readability: http://stackoverflow.com/questions/94305/what-is-quicker-switch-on-string-or-elseif-on-type#94336

Metro Smurf
+2  A: 

I think the main performance issue here is, that in the switch block, you compare strings, and that in the if-else block, you check for types... Those two are not the same, and therefore, I'd say you're "comparing potatoes to bananas".

I'd start by comparing this:

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")
{}
SaguiItay
Hi, thanks for replying! This is actually the scenario that I have. We can use either a unique id (which is a string) or the object type to differentiate between these objects.
Quibblesome
A: 

One of the issues you have with the switch is using strings, like "Bob", this will cause a lot more cycles and lines in the compiled code. The IL that is generated will have to declare a string, set it to "Bob" then use it in the comparison. So with that in mind your IF statements will run faster.

PS. Aeon's example wont work because you can't switch on Types. (No I don't know why exactly, but we've tried it an it doesn't work. It has to do with the type being variable)

If you want to test this, just build a separate application and build two simple Methods that do what is written up above and use something like Ildasm.exe to see the IL. You'll notice a lot less lines in the IF statement Method's IL.

Ildasm comes with VisualStudio...

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

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

+1  A: 

A SWITCH construct was originally intended for integer data; it's intent was to use the argument directly as a index into a "dispatch table", a table of pointers. As such, there would be a single test, then launch directly to the relevant code, rather than a series of tests.

The difficulty here is that it's use has been generalized to "string" types, which obviously cannot be used as an index, and all advantage of the SWITCH construct is lost.

If speed is your intended goal, the problem is NOT your code, but your data structure. If the "name" space is as simple as you show it, better to code it into an integer value (when data is created, for example), and use this integer in the "many times in a slow part of the app".

+1  A: 

If the types you're switching on are primitive .NET types you can use Type.GetTypeCode(Type), but if they're custom types they will all come back as TypeCode.Object.

A dictionary with delegates or handler classes might work as well.

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
}
Ted Elliott
Cute. :)Any idea if the delegate invocation have any knock-on performance effects?
Quibblesome
A: 

Three thoughts:

1) If you're going to do something different based on the types of the objects, it might make sense to move that behavior into those classes. Then instead of switch or if-else, you'd just call childNode.DoSomething().

2) Comparing types will be much faster than string comparisons.

3) In the if-else design, you might be able to take advantage of reordering the tests. If "Jill" objects make up 90% of the objects going through there, test for them first.

Mark Bessey
+11  A: 

I just implemented a quick test application and profiled it with ANTS 4.
Spec: .Net 3.5 sp1 in 32bit Windows XP, code built in release mode.

3 million tests:

  • Switch: 1.842 seconds
  • If: 0.344 seconds.

Furthermore, the switch statement results reveal (unsurprisingly) that longer names take longer.

1 million tests

  • Bob: 0.612 seconds.
  • Jill: 0.835 seconds.
  • Marko: 1.093 seconds.

I looks like the "If Else" is faster, at least the the scenario I created.

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";}}
Greg
This makes so much sense as the switch is done with strings and you have to account for the string conversion overhead. What if each class had an enum?
Rick Minerich
Is "Bob" faster because it's shorter or because it's first?
Frank Szczerba
+2  A: 

Try using enumerations for each object, you can switch on enums quickly and easily.

Rick Minerich
A: 

Remember, the profiler is your friend. Any guesswork is a waste of time most of the time. BTW, I have had a good experience with JetBrains' dotTrace profiler.

guardi
A: 

Switch on string basically gets compiled into a if-else-if ladder. Try decompiling a simple one. In any case, testing string equailty should be cheaper since they are interned and all that would be needed is a reference check. Do what makes sense in terms of maintainability; if you are compring strings, do the string switch. If you are selecting based on type, a type ladder is the more appropriate.

nt
+8  A: 

Firstly, you're comparing apples and oranges. You'd first need to compare switch on type vs switch on string, and then if on type vs if on string, and then compare the winners.

Secondly, this is the kind of thing OO was designed for. In languages that support OO, switching on type (of any kind) is a code smell that points to poor design. The solution is to derive from a common base with an abstract or virtual method (or a similar construct, depending on your language)

eg.

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

Then, instead of doing the switch statement, you just call childNode.Action()

ilitirit
+12  A: 

Greg's profile results are great for the exact scenario he covered, but interestingly, the relative costs of the different methods change dramatically when considering a number of different factors including the number of types being compared, and the relative frequency and any patterns in the underlying data.

The simple answer is that nobody can tell you what the performance difference is going to be in your specific scenario, you will need to measure the performance in different ways yourself in your own system to get an accurate answer.

The If/Else chain is an effective approach for a small number of type comparisons, or if you can reliably predict which few types are going to make up the majority of the ones that you see. The potential problem with the approach is that as the number of types increases, the number of comparisons that must be executed increases as well. if I execute the following:

int value = 25124;
if(value == 0) ...
else if (value == 1) ...
else if (value == 2) ...
...
else if (value == 25124) ... 

each of the previous if conditions must be evaluated before the correct block is entered. On the other hand

switch(value) {
 case 0:...break;
 case 1:...break;
 case 2:...break;
 ...
 case 25124:...break;
}

will perform one simple jump to the correct bit of code.

Where it gets more complicated in your example is that your other method uses a switch on strings rather than integers which gets a little more complicated. At a low level, strings can't be switched on in the same way that integer values can so the C# compiler does some magic to make this work for you.

If the switch statement is "small enough" (where the compiler does what it thinks is best automatically) switching on strings generates code that is the same as an if/else chain.

switch(someString) {
 case "Foo": DoFoo(); break;
 case "Bar": DoBar(); break;
 default: DoOther; break;
}
is the same as:
if(someString == "Foo") {
 DoFoo();
}
else if(someString == "Bar") {
 DoBar();
}
else {
 DoOther();
}

Once the list of items in the dictionary gets "big enough" the compiler will automatically create an internal dictionary that maps from the strings in the switch to an integer index and then a switch based on that index.

It looks something like this (Just imagine more entries than I am going to bother to type)

A static field is defined in a "hidden" location that is associated with the class containing the switch statement of type Dictionary<string, int> and given a mangled name

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

In some quick tests that I just ran, the If/Else method is about 3x as fast as the switch for 3 different types (where the types are randomly distributed). At 25 types the switch is faster by a small margin (16%) at 50 types the switch is more than twice as fast.

If you are going to be switching on a large number of types, I would suggest a 3rd method:

 private delegate void NodeHandler(ChildNode node);

 static Dictionary<RuntimeTypeHandle, NodeHandler> TypeHandleSwitcher =
      CreateSwitcher();
 private static Dictionary<RuntimeTypeHandle, NodeHandler> CreateSwitcher() {
  Dictionary<RuntimeTypeHandle, NodeHandler> 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...
  }
 }
}

This is similar to what Ted Elliot suggested, but the usage of runtime type handles instead of full type objects avoids the overhead of loading the type object through reflection.

Here are some quick timings on my machine:

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

On my machine at least, the type handle dictionary approach beats all of the others for anything over 15 different types when the distribution of the types used as input to the method is random.

If on the other hand, the input is composed entirely of the type that is checked first in the if/else chain that method is much faster:

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

Conversely, if the input is always the last thing in the if/else chain, it has the opposite effect:

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 you can make some assumptions about your input, you might get the best performance from a hybrid approach where you perform if/else checks for the few types that are most common, and then fall back to a dictionary-driven approach if those fail.

Sorry I missed this reply earlier. Definately deserves to be top.
Quibblesome
Great answer, +1
ajdams
A: 

If you're trying to improve the performance of a C# app, you may find this question and answer thread regarding C# profiling tools to be helpful too.

Onorio Catenacci