Optimizing the Comparison of a Variable With Multiple Options in C#
This article looks at optimizing comparison of variables for readability and performance, analyzing multiple types of object and array creation.
Join the DZone community and get the full member experience.
Join For FreeYesterday, I wrote about my thoughts around optimizing the comparison of a variable with multiple options and how I prefer to optimize for readability over theoretical or potential performance issues. After some discussion on Twitter about how my sample code might be optimized I thought it was worth doing some proper analysis.
The big potential optimizations are
- The removal of LINQ
- Not creating a new array for each check
- Removing the need for boxing
The results:
Method | Mean | Error | StdDev |
------------------------------------- |------------:|----------:|----------:|
MultipleEquals | 0.0013 ns | 0.0041 ns | 0.0034 ns |
IsOneOfObject | 132.7228 ns | 2.2074 ns | 2.0648 ns |
IsOneOfObjectWithExistingArray | 93.5959 ns | 1.7975 ns | 1.8459 ns |
IsOneOfGeneric | 63.4804 ns | 0.8762 ns | 0.7317 ns |
IsOneOfGenericWithExistingArray | 58.5615 ns | 0.8739 ns | 0.7747 ns |
IsOneOfMyEnum | 64.2691 ns | 1.3435 ns | 1.3195 ns |
IsOneOfMyEnumWithExistingArray | 58.2238 ns | 0.9457 ns | 0.8383 ns |
IsOneOfMyEnumNoLinq | 12.1887 ns | 0.2395 ns | 0.2123 ns |
IsOneOfMyEnumNoLinqWithExistingArray | 6.2302 ns | 0.0519 ns | 0.0433 ns |
Full benchmark code is at https://gist.github.com/mrlacey/1b3eef0a9945b67883486bb9540b533d.
While using multiple equality checks was by far the fastest approach, by removing LINQ, not boxing, and not creating a new array for each test I was able to achieve the best performance.
Conclusion
While using multiple equality checks is by far the fastest, I still think writing
if (MyEnum.Value1 || someVariable == MyEnum.Value2 || someVariable == MyEnum.Value3 || someVariable == MyEnum.Value6) { /// do something }
is far less preferable to
if (someVariable.IsOneOf[MyEnumNoLinq](targetValues)) { /// do something }
And the code can still be pretty darn fast. You just might need a few overloads to get the best performance from all comparisons.
Disclaimer:
Yes, I am aware that:
- I only ran the tests with enums and you might get different results with different types.
- It might not be possible to have a fixed array to compare with each time.
- There may be further optimizations available. This is good enough for me though.
- Based on the speeds involved this does feel like a micro-optimization unless you really are making such calls many, many times in quick succession.
- I haven't looked at memory usage. If you're interested, go ahead, but standard warnings about premature micro-optimizations exist.
Ok, I gave in and looked at the memory profiling too:
Method | Gen 0 | Allocated |
------------------------------------- |-------:|----------:|
MultipleEquals | - | 0 B |
IsOneOfObject | 0.0558 | 88 B |
IsOneOfObjectWithExistingArray | 0.0178 | 28 B |
IsOneOfGeneric | 0.0178 | 28 B |
IsOneOfGenericWithExistingArray | - | 0 B |
IsOneOfMyEnum | 0.0178 | 28 B |
IsOneOfMyEnumWithExistingArray | - | 0 B |
IsOneOfMyEnumNoLinq | 0.0178 | 28 B |
IsOneOfMyEnumNoLinqWithExistingArray | - | 0 B |
Yeah, I think it's safe to say that it's not an issue. If you're really concerned over 28 (or even 88) bytes you're in a really constrained environment and nothing I have to say about optimization or readability will be relevant or new to you. In fact, if you're in such a constrained environment you're probably best not writing in a managed language.
Published at DZone with permission of Matt Lacey, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments