Usually I am too lazy to bother using anything else than the ordinary ToString when working with enums, but I always have that filthy feeling that I am cheating.
If there is a dedicated method in the framework to do something, there probably is a reason for it. And knowing that the Enum class contains a GetName method to get the string representation of a enum value made me want to know if there is a performance difference between the two.
First, the tested Enum.GetName method, for sake of argument wrapped up as an Extension for easier and cleaner usage.
public static class Extension { public static string GetEnumName<T>(this T t) where T : struct, IConvertible { return Enum.GetName(typeof (T), t); } }
Back to extensions list
The test rigg
The test rigg is basically the same that I used in Performance of different lock methods in .net.Uses RunningAverage to calculate the average without storing each individual number.
Just cleaned up and simplified for this scenario:
Data
private enum SmallEnum { Small, Larger } private enum LargerEnum { a, b, c, d, e, f, g, h, aaaaa, bbbbb, ccccc, ddddd, xxxxx, yyyyy, zzzzz, VeryLongNameThisIs, OtherName, Test, oooo, olf, eowind, eeeee, ss, qqqqq, mu, miu, bach, escher, godel, code, number, last }
Decided to test 2 different enums with different number of elements to see if that has any impact. The small has 2 elements and the larger has 32.
Code
private void ExecuteTestSmall(int iterations) { var type = "small"; string s = string.Empty; try { var testData = GenerateTestDataSmall(); var toStringTimings = new RunningAverage(); var getNameTimings = new RunningAverage(); for (int i = 0; i < iterations; i++) { for (int dataIndex = 0; dataIndex < testData.Count; dataIndex++) { var item = testData[dataIndex]; toStringTimings.Add(TimeAction(() => { s = item.ToString(); })); if (!string.IsNullOrEmpty(s)) s = string.Empty; getNameTimings.Add(TimeAction(() => { s = item.GetEnumName(); })); if (!string.IsNullOrEmpty(s)) s = string.Empty; } } Log($"{type}ToString\tDataCount\t2\tIterations:\t{iterations}\tAverage:\t{toStringTimings.Average:0.000}\tticks"); Log($"{type}GetName\tDataCount\t2\tIterations:\t{iterations}\tAverage:\t{getNameTimings.Average:0.000}\tticks"); } catch (Exception ex) { Log($"{type}Fail\tDataCount\t2\tIterations:\t{iterations}\tFailed\t{ex.Message}"); } }
Ok, I was lazy. I created 2 methods instead of putting the effort to reuse it. The above is for the SmallEnum. Exactly the same code for the LargerEnum tester with difference in GenerateTestDataSmall to return a List<LargerEnum> instead of List<SmallEnum>.
This was executed as follows:
public void Execute() { Log("--------------------------------------"); Log("... single thread"); ExecuteTestSmall(250000); ExecuteTestLarger(250000); }
250000 times each.
Results
ToString | enum size | 2 | Iterations: | 250000 | Average: | 1.231 | ticks |
GetName | enum size | 2 | Iterations: | 250000 | Average: | 0.662 | ticks |
ToString | enum size | 32 | Iterations: | 250000 | Average: | 1.357 | ticks |
GetName | enum size | 32 | Iterations: | 250000 | Average: | 0.692 | ticks |
Conclusion
Using the dedicated Enum.GetName function to get the string representation instead of the lazy object.ToString() seems to be twice faster. But in the end, we are talking around 1 tick, so maybe not the first place to start optimizing if you have a performance issue in you application. As I wrote above, it just feels a little bit lazy to not do it the correct way ;)
All code provided as-is. This is copied from my own code-base, May need some additional programming to work. Hope this helps someone out there :)
No comments:
Post a Comment