Understanding and applying the principles of memory management is key to building high-performing managed applications.
- By Peter Varhol
Sometimes, poorly performing code can seem like a riddle with no answer. You can find your way through the .NET Framework, write C# code until it comes out of your ears and work the debugger to get an app that runs well and does all the right things --but still, your application's performance falls short of your goal.
Even more vexing is when you've written only a few thousand lines of code, with copious calls to the Framework, which makes it difficult to figure out how you might be gumming up the works. Microsoft takes every opportunity to remind us how the .NET Framework and runtime are optimized for performance, but the problem simply can't be in your code --because your code is pretty simple, really.
The majority of it seems to execute in the Framework, or perhaps in DLLs and COM objects. You really just set up some data structures, make database calls, run your business logic and write results back into the database. Of course, if your app runs across more than two tiers, as a Web app would, the level of complexity becomes much higher, even though you may not be writing much more code.
Ah, but that combination is potentially lethal if you don't understand managed code. If you write managed code in the same way you've written unmanaged code in the past, you're likely to be unpleasantly surprised. The rules are different, and a part of our tradecraft is to learn and apply the new rules.
Learning the Ropes
To many, these rules are counterintuitive. Memory management is perhaps least intuitive to experienced C++ programmers, who are used to managing most memory details directly from their code. In particular, in C++ memory allocation is a relatively expensive operation, whereas freeing memory is cheap. The opposite is true of managed memory: allocation is relatively inexpensive, while freeing --and the resulting garbage collection --is quite expensive.
The difference may seem trivial, but memory management drives a lot of decisions in unmanaged code. Many developers believe that automatic memory management removes the need to manage memory at all, and that's not entirely true. What it does is take away most of the complex tedium of allocating and deallocating memory. What it doesn't do is remove any responsibility on the developer's part to understand what's happening in memory, and to influence how memory is used. Influencing memory use can't be done directly, which is part of the new challenge of writing managed applications.
Managed Memory Problems
Perhaps the best way to learn about memory management under the .NET Framework is to write code and identify performance problems that result from poor coding decisions, and to examine other code that may be within your organization. You might find, for example, that large numbers of short-lived objects are being created, requiring the garbage collector to work harder than normal.
You might be able to see the results of code that uses memory poorly, but how can you find the underlying causes? The first way is static code analysis -- Microsoft's FxCop or a third-party static-analysis tool can examine how you set up your data structures to determine if you have the potential to create large objects, or many small objects. It will also warn you if you invoke the GC.Collect() method yourself, because forcing a garbage collection will actually make your code slower much of the time.
Once you've built your code, you can run a code profiler, which will tell you how long you spend in a particular method -- or even line of code -- and how many times you execute that code. The code profiler, however, is of limited usefulness unless you can supplement it with a memory analyzer. The memory analyzer shows the creation of objects and the object lifecycle while your application is running.
Putting it All Together
Here's how you can use the two together. The code profiler shows you where in your code the application spends most of its time. Once you find the slow code, you use the memory analyzer to look at the number and size of objects and where those objects are coming from, and then compare that to what your code is doing at that point.
Even if you're not yet having problems with your managed code, using these tools is a way to unlearn certain coding behaviors and become proficient in others. Understanding and applying the principles of memory management is key to building high-performing managed applications.
Peter Varhol is the executive editor,
reviews of Redmond magazine and has more than 20 years of experience as a software
developer, software product manager and technology writer. He has graduate degrees
in computer science and mathematics, and has taught both subjects at the university