Virtual Developer Workshop: Containerized Development with Docker
As .NET programmers, we can become a little lazy when it comes to performance optimization. What difference does it make? There are half a dozen ways to do anything and most of them perform more or less the same, right? It's all fixed in the IL....
The truth is that, very often, the way you code your application and the data structures you use can make a huge difference in your application's performance. These bullets call out some of the important difference and give you some hard facts you can keep in mind when weighing the trade-offs.
- Use Dispose instead of relying on a Finalize event. Dispose is a method you create and it must be explicitly called when you no longer need the object—so it's a completely manual process, whereas the Finalize event always occurs automatically. However, Finalize doesn't occur until the object is garbage collected and can take 20 times as long to execute. Don't forget to call GC.SupressFinalize in your Dispose method.
- Avoid exceptions whenever practical. Although exception handling dramatically cleans up your code, it comes with a hefty runtime performance hit. Never use it to simply pass data back to the calling routine in a non-exceptional circumstance. And, whenever possible, run interference before the action to reduce the chance of exceptions being triggered—like checking for a file's existence before opening it rather than handling a does-not-exist exception.
- When designing your value types and class hierarchy, remember that value types perform much faster than reference types because they are allocated on the stack, whereas reference types are allocated on the heap.
- But be careful! The benefits of using a value type can quickly be eaten up if you frequently use the value type as if it were a reference type. For example, if you put a value type into a collection that holds object types. This is called boxing and it really eats processor cycles, especially if your code goes back and forth between using it as a value (doing math on it) and using it as a reference type.
- Minimize type conversions by using the most specific type possible to describe your data.
- Use generics to create collections and other data structures so that, in use, they can be instantiated to hold exactly the types needed. This avoids both time consuming tasks of boxing/unboxing and of type conversion.
- In C#, use as, not is. The is keyword is used to see whether a reference can be cast as particular type, but it doesn't return a reference converted to that type. So usually, if you get a positive result from the is, the first thing you'll do is a cast—effectively implementing the same cast twice. With the as keyword, a reference cast as the new type is returned if it's valid and a null is returned if it's not. You then can check for the null and do what you like. The as approach is fully 50% faster than the is approach.
- If you're doing lots of string concatenation, always use StringBuilder. It reduces memory consumption and can result in performance benefits of an order of magnitude or more.
- Is your string empty? Check to see whether its Length is 0 rather than comparing to "" or String.Empty. It's three times faster.
- The use of optional parameters and default parameter values in Visual Basic is not a feature of the CLR, but rather one implemented by Visual Basic itself. However, Visual Basic implements this feature at compile time, so there is no significant performance downside to using them.
- When using both managed and unmanaged code in a C++ project, minimize the amount of context switching between the two. And, be sure to use unmanaged code only in cases where performance is significantly enhanced (or where it is necessary for other reasons). Too much context switching can reduce or eliminate any performance benefits the unmanaged code provides.
# # #