If vs Switch Performance, To Switch Or Not To Switch

Introduction

While most people would generally consider spending vast amounts of energy arguing over minute details bizarre; for programmers this kind of weirdness with a taint of madness comes with the territory.

If vs Switch Performance

Recently a nerd squabble came up and it was centered around the use of an “if” statement versus a “switch” statement. I found myself intrigued and curious enough that I decided to take a more scientific approach and put real numbers instead of just nerd speculation.

The Pattern

A common pattern in programming is to return a value when presented with another associated value. In this particular case an additional condition was to return a default value when no match existed.

Clearly given the above description the text book answer most probably would be to use a switch statement. Normally I would also use a switch statement given the above pattern. However in our particular case there was only three associated values (with the first one being the default as well).

Me being the coding rebel, the white space bureaucrat and line break conservative I opted in this situation to use two simple inlined “if” statements. The two coding styles are shown below:

Classical Switch

string department;

switch (departmentCode)
{
    case "FD":
        department = "Finance Department";
        break;
    case "HR":
        department = "Human Resource";
        break;
    case "IO":
    default:
        department = "Information Office";
        break;
}

Rebellious If

string department = "Information Office";
    
if (departmentCode == "FD") { department = "Finance Department";   }
if (departmentCode == "HR") { department = "Human Resource";       }

Switch Visual “Flow”

switch visual flow

If Visual “Flow”

if visual flow

Such rebellious coding style did not go down well with the fellow programmers and predictably began the squabble. I prefered my use of inlined “if” statements for no other reason than aesthetics, I assumed that given the context the performance differences would be entirely negligible.

A Scientific Approach

Later in the evening I started wondering about the two cases and what the actual performance between these where. Looking at some stack overflow questions it appeared that the general view seemed to be that switch statement yielded better performances. However as with all things it really depended on context. In some cases the “if” statement was more performant. This got me even more curious. With compiler optimization added to the mix the question became even less clear cut.

I decided to do some actual benchmarks and see what the numbers looked like. At least this would be more objective than just outright speculation about what “might” happen.

Reading about the “if” versus “switch” performances, I found that after a certain point the compiler would optimize a switch statement by using a “jump table”. So I decided to also test an expanded case from our own specific case.

Results 1 (Short Case)

A simple benchmark program was created and the two styles run for 10 million cycles for each code path. The entire test was then repeated for a total of 7 trials. All tests where compiled with the “optimized” checkbox turned on. The final averaged results are shown below:

Results 1

The disassembled IL code produced by the “if” style is shown below:

<br /> test:<br /> IL_0000: ldstr "Information Office"<br /> IL_0005: stloc.0 // department<br /> IL_0006: ldarg.1<br /> IL_0007: ldstr "FD"<br /> IL_000C: call System.String.op_Equality<br /> IL_0011: brfalse.s IL_0019<br /> IL_0013: ldstr "Finance Department"<br /> IL_0018: stloc.0 // department<br /> IL_0019: ldarg.1<br /> IL_001A: ldstr "HR"<br /> IL_001F: call System.String.op_Equality<br /> IL_0024: brfalse.s IL_002C<br /> IL_0026: ldstr "Human Resource"<br /> IL_002B: stloc.0 // department<br /> IL_002C: ldloc.0 // department<br /> IL_002D: call System.Console.WriteLine<br /> IL_0032: ret<br />

The disassembled IL code produced by the “switch” style is shown below:

<br /> test:<br /> IL_0000: ldarg.1<br /> IL_0001: dup<br /> IL_0002: stloc.1 // CS$0$0000<br /> IL_0003: brfalse.s IL_003E<br /> IL_0005: ldloc.1 // CS$0$0000<br /> IL_0006: ldstr "FD"<br /> IL_000B: call System.String.op_Equality<br /> IL_0010: brtrue.s IL_002E<br /> IL_0012: ldloc.1 // CS$0$0000<br /> IL_0013: ldstr "HR"<br /> IL_0018: call System.String.op_Equality<br /> IL_001D: brtrue.s IL_0036<br /> IL_001F: ldloc.1 // CS$0$0000<br /> IL_0020: ldstr "IO"<br /> IL_0025: call System.String.op_Equality<br /> IL_002A: brtrue.s IL_003E<br /> IL_002C: br.s IL_003E<br /> IL_002E: ldstr "Finance Department"<br /> IL_0033: stloc.0 // department<br /> IL_0034: br.s IL_0044<br /> IL_0036: ldstr "Human Resource"<br /> IL_003B: stloc.0 // department<br /> IL_003C: br.s IL_0044<br /> IL_003E: ldstr "Information Office"<br /> IL_0043: stloc.0 // department<br /> IL_0044: ldloc.0 // department<br /> IL_0045: call System.Console.WriteLine<br /> IL_004A: ret<br />

The switch produces a slightly longer IL code.

Results 2 (Extended Case)

The simple benchmark program was extended and the two new styles run for 10 million cycles for each code path.

Results 2

The disassembled IL code produced by the “if” style is shown below:

<br /> test:<br /> IL_0000: ldstr "Information Office"<br /> IL_0005: stloc.0 // department<br /> IL_0006: ldarg.1<br /> IL_0007: ldstr "FD"<br /> IL_000C: call System.String.op_Equality<br /> IL_0011: brfalse.s IL_0019<br /> IL_0013: ldstr "Finance Department"<br /> IL_0018: stloc.0 // department<br /> IL_0019: ldarg.1<br /> IL_001A: ldstr "HR"<br /> IL_001F: call System.String.op_Equality<br /> IL_0024: brfalse.s IL_002C<br /> IL_0026: ldstr "Human Resource"<br /> IL_002B: stloc.0 // department<br /> IL_002C: ldarg.1<br /> IL_002D: ldstr "MI"<br /> IL_0032: call System.String.op_Equality<br /> IL_0037: brfalse.s IL_003F<br /> IL_0039: ldstr "Mangement Information"<br /> IL_003E: stloc.0 // department<br /> IL_003F: ldarg.1<br /> IL_0040: ldstr "SO"<br /> IL_0045: call System.String.op_Equality<br /> IL_004A: brfalse.s IL_0052<br /> IL_004C: ldstr "Security Officer"<br /> IL_0051: stloc.0 // department<br /> IL_0052: ldarg.1<br /> IL_0053: ldstr "HO"<br /> IL_0058: call System.String.op_Equality<br /> IL_005D: brfalse.s IL_0065<br /> IL_005F: ldstr "Head Office"<br /> IL_0064: stloc.0 // department<br /> IL_0065: ldloc.0 // department<br /> IL_0066: call System.Console.WriteLine<br /> IL_006B: ret<br />

The IL code produced is an extenstion of the shorter version.

The disassembled IL code produced by the “switch” style is shown below:

<br /> test:<br /> IL_0000: ldarg.1<br /> IL_0001: dup<br /> IL_0002: stloc.1 // CS$0$0000<br /> IL_0003: brfalse IL_00BF<br /> IL_0008: volatile.<br /> IL_000A: ldsfld <PrivateImplementationDetails>{6D2970D2-35A8-4EC1-AA32-6B4B8F1B6F55}.$$method0x6000002-1<br /> IL_000F: brtrue.s IL_0066<br /> IL_0011: ldc.i4.6<br /> IL_0012: newobj System.Collections.Generic.Dictionary<System.String,System.Int32>..ctor<br /> IL_0017: dup<br /> IL_0018: ldstr "FD"<br /> IL_001D: ldc.i4.0<br /> IL_001E: call System.Collections.Generic.Dictionary<System.String,System.Int32>.Add<br /> IL_0023: dup<br /> IL_0024: ldstr "HR"<br /> IL_0029: ldc.i4.1<br /> IL_002A: call System.Collections.Generic.Dictionary<System.String,System.Int32>.Add<br /> IL_002F: dup<br /> IL_0030: ldstr "MI"<br /> IL_0035: ldc.i4.2<br /> IL_0036: call System.Collections.Generic.Dictionary<System.String,System.Int32>.Add<br /> IL_003B: dup<br /> IL_003C: ldstr "SO"<br /> IL_0041: ldc.i4.3<br /> IL_0042: call System.Collections.Generic.Dictionary<System.String,System.Int32>.Add<br /> IL_0047: dup<br /> IL_0048: ldstr "HO"<br /> IL_004D: ldc.i4.4<br /> IL_004E: call System.Collections.Generic.Dictionary<System.String,System.Int32>.Add<br /> IL_0053: dup<br /> IL_0054: ldstr "IO"<br /> IL_0059: ldc.i4.5<br /> IL_005A: call System.Collections.Generic.Dictionary<System.String,System.Int32>.Add<br /> IL_005F: volatile.<br /> IL_0061: stsfld <PrivateImplementationDetails>{6D2970D2-35A8-4EC1-AA32-6B4B8F1B6F55}.$$method0x6000002-1<br /> IL_0066: volatile.<br /> IL_0068: ldsfld <PrivateImplementationDetails>{6D2970D2-35A8-4EC1-AA32-6B4B8F1B6F55}.$$method0x6000002-1<br /> IL_006D: ldloc.1 // CS$0$0000<br /> IL_006E: ldloca.s 02 // CS$0$0001<br /> IL_0070: call System.Collections.Generic.Dictionary<System.String,System.Int32>.TryGetValue<br /> IL_0075: brfalse.s IL_00BF<br /> IL_0077: ldloc.2 // CS$0$0001<br /> IL_0078: switch (IL_0097, IL_009F, IL_00A7, IL_00AF, IL_00B7, IL_00BF)<br /> IL_0095: br.s IL_00BF<br /> IL_0097: ldstr "Finance Department"<br /> IL_009C: stloc.0 // department<br /> IL_009D: br.s IL_00C5<br /> IL_009F: ldstr "Human Resource"<br /> IL_00A4: stloc.0 // department<br /> IL_00A5: br.s IL_00C5<br /> IL_00A7: ldstr "Mangement Information"<br /> IL_00AC: stloc.0 // department<br /> IL_00AD: br.s IL_00C5<br /> IL_00AF: ldstr "Security Officer"<br /> IL_00B4: stloc.0 // department<br /> IL_00B5: br.s IL_00C5<br /> IL_00B7: ldstr "Head Office"<br /> IL_00BC: stloc.0 // department<br /> IL_00BD: br.s IL_00C5<br /> IL_00BF: ldstr "Information Office"<br /> IL_00C4: stloc.0 // department<br /> IL_00C5: ldloc.0 // department<br /> IL_00C6: call System.Console.WriteLine<br /> IL_00CB: ret<br />

This longer swith case produces much more IL code, the compiler optimization is done using a .NET dictionary.

Conclusion

Ok so what can we conclude from the raw results? we can certainly say that for this particular pattern the data indicates that the “if” statement is indeed faster and produces smaller IL code.

Does this mean it is conclusive? No, because in most practical real world cases such extreme number of cycles will not typically be used. The performance difference will always be practically zero.

Some may argue that in the above case that the “if” suffers from “bad practice” because it instantiates a string variable and in all but the default case overrides its straight away and thus “wasteful”. This line of argument much like the performance red herring in reality makes no difference because a very large number of objects would have to be created for any real performance degradation.

What it ultimately boils down to is programmer preference, arguments based on performance are irrelevant since they are a massive premature optimization that for 99% of the time do not even apply.

“Beautiful code, is in the eye of the beholder”

Until next time beautiful coding!

comments powered by Disqus