Any post about .NET application performance naturally has to start with a tip about StringBuilder. And this post is no exception. Make sure that you use StringBuilder when concatenating strings because the “+” operator…
I’m just kidding. I mean, that’s not bad advice. Rather, I’m kidding in that I just don’t want to offer this style of tip today.
Often, advice about application performance comes at you with the staccato of rapid fire tips. “37 Ways to Shave Nanoseconds off of Your Overnight ETL Process.” I exaggerate for comedic effect with that title, perhaps. But the point remains. Authors serve up advice for optimization with many tips that matter only inside of tight loops. And it’s hard to keep all of those in your head.
I’d like to take a different tack here today and offer fewer suggestions in the hope that they’ll stick with you. But I differentiate my message in another crucial way as well. I want to offer performance tips for .NET programming that will also help you write clean, readable code.
People seem to feel that these concepts must stand in opposition to one another. Those offering performance tips demand changes that produce the desired effect, even as they make the code harder to read. This phenomenon gives rise to the iconic wisdom that “premature optimization is the root of all evil.” Implementing them might bring relatively insignificant marginal gains in performance while creating confusing, hard-to-read code.
But it strikes me that readable and performant sometimes overlap. And, since tips for that serve two purposes, they are definitely worth filing away.
Be Careful with Reflection
Nearly two decades ago, I cut my teeth with C and C++ as a young programmer. So some years later when I’d started with Java, my reaction to reflection was, “wait, you can do what?!” The idea that I could have runtime access to the source code names of my variables dazzled me with possibilities.
As a result, please believe me when I say that I recognize both the coolness and power of reflection. To bring in a bit of context, reflection grants language users the ability to examine and modify source code at runtime. This lets you do things from dynamic GUI construction to writing (iffy) unit tests of private methods to slick convention over configuration framework implementations. The world becomes your oyster.
But it becomes your oyster at a cost, and one that you may overlook. In .NET, reflection works by reading the CLR’s metadata, an operation akin to performing I/O operations. And, as with reading from or writing to files, the performance implications of that can pile up quickly.
On top of that, the same power that reflection provides can mask cause and effect relationships. When your codebase treats everything named “XController” a certain way and has behaviors defined by method and property attributes, it tends to mystify new developers and introduce indirection to debugging. It seems like magic. And, while cool, magic hardly makes for easy maintenance.
So reflect with care, both for performance and for maintainability.
Be Judicious with Exceptions
When targeting the .NET framework, exceptions serve an important function in your code. As the name suggests, they give you a tool for handling exceptional situations. Out of the box, you get a way to jump out of your application’s normal flow and obtain all sorts of debugging information about the call stack.
But that construct comes at a price, both in terms of code readability and performance. The readability side has obvious downside. The try/catch/finally blocks clutter your code and create extra exit points from a method that you have to reason through. The performance side happens only when the exceptions are thrown, but that gets expensive. Normal execution stops while the CLR works through the call stack looking to handle the newly-created and thrown exception object.
Now, if you’re using exceptions judiciously, this is entirely appropriate. When something goes seriously wrong, performance tends not to be a top concern. But if you’re using exceptions all over the place in lieu of return statements and control flow, you’re going to pay the price here, both in readability and performance. Save your exception handling code for truly exceptional situations.
Think Through Collection Types
In .NET, you have an embarrassment of riches with collection types, in my opinion. List, Dictionary, Collection, Hashset, Stack, Queue, and plenty of others round out the field. And then, of course, you have the humble primitive array type. You never lack for an appropriate type.
But you can fall into the trap of using one with more horsepower than you need. Generally speaking, the various collection types encapsulate and wrap a simple array, which yields the best performance. After all, with the array you find yourself closest to the metal. The extra bells and whistles on those types don’t come for free; those instances incur overhead cost.
Going into the nitty gritty details would be beyond the scope of this particular post. Here, I just want to make you aware that you should do your homework and be aware of what you need. Don’t use List to temporarily index 3 numbers just because you think you may some day want to sort them or because you like the cool Linq extension methods you get. Reason through what you actually need and understand the trade-offs of these types.
Obviously that’s going to help with performance, but it also helps with comprehension of your code. Use only as much type as you need, and your intent becomes clearer to maintainers of your code.
Make Stateless Methods Static
The last point that I’ll mention is one that I sometimes see called out by linting type tools and productivity add-ins at times. With this one, I offer the advice to make your methods static when they do not refer to any instance state. (Hopefully they also don’t refer to global state, but that’s a different blog post)
On the performance side, this makes a bit of a difference. Static methods exist outside of instance considerations, which means slightly less overhead in dealing with the method at runtime, all else being equal. This probably won’t make the difference between freezing someone’s machine and snappy performance, but it won’t hurt.
Where this matters even more is on the clean code side. Making instance-less methods static signals design intent and broadcasts that the method does not access or mutate instance state.
For maintainers, this helps draw a clear delineation between cohesive instance logic and accessory static logic. In either case, this presents enough of a benefit that tools doing static analysis suggest it, so I’d adopt the practice.
And, Finally, Assume Nothing
The last thing that I’ll say with regarding to improving performance via maintainable code is that you should avoid cargo cult approaches. In this post, I’ve offered tips that provide value in at least two ways each, so, even if they didn’t theoretically net you much gain, they would improve maintainability.
But don’t settle for adopting an optimization because you read it on a blog. Run time trials to prove it, and keep those results around when discussing with your peers. Back your claims with evidence, and keep the ones that prove significant. And, perhaps most importantly, create performance tests for your codebase. This lets you easily test out different techniques to see what works. But it also lets you see whether or not the optimizations are even worth doing. If your code already passes the tests with flying colors, focus on other priorities.
In software development, endless trade-offs greet you far more often than absolutes. Follow the advice I’ve laid out to improve performance and readability. But above all, make sure you measure so you know what needs improving.