I’ve been doing a lot of very intense .Net coding at work for the last few months and it’s very gratifying to see that .Net can handle some pretty serious high-load scalable server work. We are doing some pretty amazing things. However, there are all sorts of gotchas—you do have to learn how to do it correctly (but that’s true of any technology).
One of the things we’re most concerned about right now is performance, and where performance is concerned, one of the big gotchas is the .Net Framework Class Library. Don’t get me wrong, for 99.99% of all .Net projects, the performance is perfectly adequate and you don’t have to care (much). On the other hand, if you’re dealing with highly-scalable systems, you should definitely validate the performance of any general-purpose library you use.
As with all performance question, the only 100% solid advice in all situations is: measure, measure, measure.
General Purpose Methods May Not Be Fast
I did a profiler run on one of our components the other day and a few methods showed up that I would never expect to see in a profiler trace:
We were calling these methods a lot. In my mind, they both basically boiled down to logical AND (&) statements. Unfortunately that’s not the case.
If you use an IL decompiler like ILSpy to examine the code of these methods, you’ll see they do quite a bit more. For example, HasFlag also checks whether the types are equal—useful, but that’s a lot more expensive than a simple & check.
IsDefined is even worse. There are half a dozen checks for specific types, followed by an allocation of an array containing all the enum values, and finally a binary search. If your enums are string types, then it’s just a linear search with string comparisons.
It’s easy to understand why these methods are so heavy—they’re being extra cautious for correctness-sake. That’s probably what you want in a general-purpose framework.
However, if your goal is performance and you have a closed system, complete control of your code, and adequate tests, then you can make better decisions.
Rather than do this:
Do this instead:
if ((bookFlags & BookTypes.Fiction) != 0)
As for IsDefined, just don’t use it. Or use it in debug-only assertions to validate functionality.
Another Enum method to avoid if you can is ToString(), especially on enumerations that have [Flags].
Suppose you have this code:
Exception = 1,
Failure = 2,
Canceled = 4,
TimedOut = 8,
AnyFailure = Exception | Canceled | TimedOut
You may be tempted to do this:
This will not work. It will check that ALL of the bits are set, not just one of them.
You will need to do this:
if ((node.Flags & NodeFlags.AnyFailure) != 0)
We saw that around 5% of our CPU time was being spent just in Enum.IsDefined and Enum.HasFlag. With these changes, we reduce that to approximately 0.
If you find tips like these helpful, you’ll love my book, C# 4.0 How-To.