Is Your App a Memory Hog?
The Windows NT, 2000, and XP operating systems provide performance counters. For some unknown reason, however, Microsoft implemented these performance counters in bizarre fashion at the underlying operating-system level.
If you accessed the counters natively, without using helper libraries, you would need to make calls to the RegQueryValueEx Windows API, interrogate the variable-length data that the API returned (which is stored as nested-nested-nested-memory blocks), and perform lookups on a special "Counter 009" registry key.
As if this procedure isn't odd enough, RegQueryValueEx doesn't even query a registry key; it loads up performance DLLs and calls different entry points to retrieve data. For example, once you have the raw data, you need to convert the values from numbers into percentages or time differences. All told, you'll need to carry out about 20 common calculations.
All in all, this is a very time-consuming bit of programming to do yourself. Fortunately with .NET, all you need to do is instantiate a PerformanceCounter object and interrogate it. The code sample in Listing 1, which took five minutes to write, replaces what took a week to write pre-.NET. Visual Studio .NET auto-generated most of it, and I had to type in only six lines myself. The snippet examines the value of a particular performance counter five times and displays the results.
Still, no matter how clean this code is, and no matter how well Microsoft has hidden the underlying Windows API, it is still called at some level during optimization. With the six lines of code I added, the .NET garbage collector is highly efficient, and allocating and reclaiming memory is well optimized, but instantiating a PerformanceCounter object and calling NextValue() five times allocates more than 17,000 objects on the heap and grabs 1.1MB from memory. That might not be significant for your particular application, but it is certainly worth your attention.
The graph in Figure 1 shows the number of allocations for each object type, while the graph in Figure 2 shows memory allocated for each object.
|Figure 1: Number of Allocations for Each Object Type |
|Figure 2: Memory Allocated for Each Object |
In short, those six lines of code will allocate 8,000 strings that take up a total of 500Kb of memory. That might or might not represent a performance hit, but I've found that it certainly surprises most developers.
With tools like the .NET memory profiler, you can determine what objects are allocated on the heap, what creates them, and how long they stay there before being garbage collected. You might not think you need to worry about memory allocation with .NET, but performance hits like those I've discussed in this section should persuade you otherwise.