Home > Guides > Programming > .NET and Windows Programming

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

Best Practices — Performance

Last updated Nov 11, 2004.

Many programmers are obsessed with performance issues, wanting to write "the fastest code" in all situations, even when doing so doesn't provide any real benefit. And all too often, programmers concentrate their optimization efforts in the wrong place: on areas of the program that aren't executed often enough to be worth the effort.

For line of business applications, performance is primarily a design and infrastructure consideration. When combined with good algorithm selection, a good design running on the right hardware will perform quite well with no particular emphasis having been placed on performance by the application developer. That said, there are some places in which a simple coding decision can result in a huge gain or loss in performance. There are many such places and I can't cover them all here, but I will touch on those that have been found to provide a good performance boost with relatively little effort.

Garbage Collection

Contrary to the myth, automatic memory management is not significantly slower than doing it yourself. The .NET memory manager provides very fast--almost instantaneous--allocations, and the process of cleaning up (garbage collection) is optimized so as to be as unobtrusive as possible. In general, garbage collection "just works", and you don't have to mess with it.

The System.GC class controls the garbage collector, and has a method called Collect that you can call to force a garbage collection. Don't do this! The garbage collector keeps track of the state of memory and "knows" when to collect for best performance. Applications that try to help the garbage collector by calling GC.Collect periodically usually end up running slower than those that just let the garbage collector do its thing.

An issue related to automatic memory management is deallocation of unmanaged resources. Because the garbage collector doesn't run at any set time, there is no deterministic finalization of objects. Objects are finalized when the garbage collector gets around to doing it. This can cause trouble if you are using scarce unmanaged resources. The .NET Framework includes a Dispose design pattern that was designed precisely to address this issue. Used correctly, the Dispose pattern will allow you to free unmanaged resources deterministically while still working within the recommendations of the garbage collector. See the articles linked below, and study the Automatic Memory Management topic in the .NET SDK documentation for information on how to implement the Dispose pattern and IDisposable interface.

String Concatenation

By definition in the Common Language Specification (CLS) any object of type String is immutable. This means that once the object is created, its contents cannot change. When a string is modified, the runtime creates a new string and returns it, leaving the original to be garbage collected. This is fast and simple for infrequent string concatenations, but when you're repeatedly modifying a string, the overhead of collecting those scraps can seriously degrade performance. If you must repeatedly modify a string, use a StringBuilder object with an appropriate initial buffer size rather than direct string manipulation. Just as a rough indicator, string concatenation is anywhere from 10% to 500% slower than using a StringBuilder.

Reference Types versus Value Types

You should use simple structs rather than classes when you can get away with it. Allocating and initializing a value type (struct) is an order of magnitude faster than creating an instance of a class. Member access is about the same. Value types are perfect for small structures like points, sizes, vectors, and other simple types, and using them can noticeably increase performance. However, they are a lot less flexible than reference types and they can actually hurt performance if you're continually treating them like reference types. The reason is that in order to treat a value type like a reference type, the runtime must perform "boxing" and "unboxing" operations. The overhead of these operations causes the use of value types to be more expensive than corresponding reference types.

Boxing and unboxing occur any time you treat a value type like a reference type. This includes passing value types to methods that expect object parameters, and when you add a value type to an array, collection, or hash table.

Caching

Output caching can provide a huge performance boost for your ASP.NET applications, at very little cost. On server versions of the operating system, you have a lot of options for tweaking the use of caches. Make sure that you keep caching in mind when designing your ASP.NET applications. Using output caching, one of the techniques available to web applications, can mean a difference of 10 times the number of requests per second that your application can serve.

Throw Fewer Exceptions

Throwing exceptions can be very expensive, so make sure that you don't throw a lot of them. Use the performance monitor to see how many exceptions your application is throwing. You might be surprised to find that certain parts of your application throw a lot more exceptions than you expected. You can realize a good performance gain by finding and designing away exception-heavy code.

Note that I'm not talking about try/catch blocks here, which are very performant in the normal case. The only time you incur overhead with try/catch is when an exception is thrown—something that should be relatively rare. And then, the benefit of the catch far outweighs the performance cost.

You should avoid using exceptions for control flow and for not-so-exceptional things like end of file. Save exceptions for the truly exceptional--operations that cannot complete, or unexpected errors that crop up in normal operation.

The runtime can throw exceptions even if your code doesn't. In addition, COM HRESULT values are returned as exceptions during interop calls. You should use the performance monitor to see how many exceptions you are throwing, and use the debugger to check the source of those exceptions if you find that they are excessive.

Further Reading

Writing high performance .NET applications is a much deeper topic than I can cover in this small article. The items above are among the most important, but certainly not exhaustive. You can gain a better understanding of this topic by studying these two articles from MSDN:

Performance Tips and Tricks in .NET Applications

Performance Considerations for Run-Time Technologies in the .NET Framework

Discussions

Copies of the array?
Posted Dec 23, 2008 03:40 PM by luige21
1 Replies
Hi
Posted Dec 5, 2008 05:10 AM by ajay2000bhushan
2 Replies
You have no clue.
Posted Jun 10, 2008 03:28 PM by theinternetmaster
1 Replies

Make a New Comment

You must log in in order to post a comment.

Related Resources

Jim Mischel"Highly unlikely" does not mean "impossible"
By Jim MischelJuly 18, 2009 No Comments

One of my programs crashed the other day in a very unexpected place.  A call to System.Threading.ConcurrentQueue.TryDequeue (from the Parallel Extensions to .NET) resulted in an OverflowException being thrown.  Investigation revealed a pretty serious bug in the System.Random constructor.

It's Here; Put Away Your Pre-Conceptions on What an OS Must Be: Part II
By John TraenkenschuhMay 24, 2009 No Comments

In the last blog in this series, Traenk relates his first experiences with computers and with coding.  But now, some years have passed. . .

It's Here; Put Away Your Pre-Conceptions on What an OS Must Be: Part I
By John TraenkenschuhMay 24, 2009 No Comments

Traenk relates his past experience with Operating Systems that goes back 25 years, ok, more than that but he ain't tellin'

See More Blogs

Informit Network