an actual controversy switch vs if chain (a benchmark example using LinqPad 7 free edition), but the real problem was hidden in a different place [refactoring stuff]
In Linqpad 7 user can execute BenchmarkDotNet, as I love LinqPad for it is simplicity and ability to test somestuff without the burden of creating a project.
After installation (of LinqPad) we can find a predefined query called BenchmarkDotNet, with an example benchmarks defined, so we can start 'doing stuff'
After first run we can see that all methods with attribute [Benchmark] are executed in the harness, so we can start digging.
Right click on the query and select use as a template - so we will not use the orginal one.The simple code is here: gist (and at the end of this post)
Creating a class for each method allows to have it nicely displayed on the results pane.
From the benchmark results we can see that there is nothing to worry with the practicular code as both methods have fairly same time of execution - and let's try to add more steps (up to 10 and see if we will be still close or switch will take an advantage)
And the results are very interesting (who is using switch with a string you idiot)... Switch statement with 10 cases had an very similar execution time, where (as expected) our if chain grows it's execution time almost in linear way with the amount of cases.
In fact, what we can see here is a change from a check to hash process inside a switch statement (according to this answer switch changes it's behaviour after 6th element is added).Now the question is what we need. In most cases the if else statement tree will be used as this is easy to write but harder to maintain over the time - let's see how it performs with 20 cases.
As we can see above that switch timing is stable and more efficient when we are operating on a larger number of cases. Moreover we can see that BenchmarkDotNet is adjusting executed number of operations dynamically as per two instances with time >12ns we have half of the value.
But still is the question: why we are testing this? - especially that the tested snipped do not have any logic, so use case can't be inherited for a learning purpose, but let's do another one. Let's replace strings with integers and see if we can get some picoSeconds off the table :) .
So the results are very interesting, as we can see that after 4th element, the overhead used to create switch statement is fully compensated by execution speed.
The idea to benchmark if vs switch started during code review process for a user activity tracing on a page. The if statement was used to map a category to DTO, which in my opinion is a nonsense, and let my try to explain why.
Having a DTO like below, forces us to make this if/else chain to assign a timestamp to a particular action in a linear process when a document is processed (just abstracted to avoid breaking NDP here).
The way that the solution was implemented allowed to overwrite action time when a user navigated back to previous step or repeated a particular step (so the timing was off by that).
As most can say that this DTO looks like de-normalized structure (as it is having an aggregated data in it), that this mapping is necessary especially using a PATCH method on the API side.
So simplifying our DTO to shape below - will reduce compute server side, client side and will allow to preserve data that is overwritten when a user repeats an action (so question is: why the action is repeated, do we have a data problem, is there a process issue, or maybe the UI sucks a lot).
listin 01
public class SwitchStuff
{
[Benchmark]
[Arguments("ThisIsCase1")]
[Arguments("ThisIsCase2")]
[Arguments("ThisIsCase3")]
[Arguments("ThisIsCase4")]
[Arguments("ThisIsCase5")]
public string ProcessSwitchWithString(string matchCase)
{
switch (matchCase)
{
case "ThisIsCase1":
return "case1";
case "ThisIsCase2":
return "case2";
case "ThisIsCase3":
return "case3";
case "ThisIsCase4":
return "case4";
case "ThisIsCase5":
return "case5";
}
return string.Empty;
}
}
public class IfStuff
{
[Benchmark]
[Arguments("ThisIsCase1")]
[Arguments("ThisIsCase2")]
[Arguments("ThisIsCase3")]
[Arguments("ThisIsCase4")]
[Arguments("ThisIsCase5")]
public string ProcessIfWithString(string matchCase)
{
if (matchCase == "ThisIsCase1")
{
return "case1";
}
else if (matchCase == "ThisIsCase2")
{
return "case2";
}
else if (matchCase == "ThisIsCase3")
{
return "case3";
}
else if (matchCase == "ThisIsCase4")
{
return "case4";
}
else if (matchCase == "ThisIsCase5")
{
return "case5";
}
return string.Empty;
}
}
Comments
Post a Comment