Welcome!

.NET Authors: Liz McMillan, Peter Silva, Yakov Werde, Matthew Pollicove , Corey Roth

Related Topics: .NET

.NET: Article

Cover Story: Garbage Collection

How many managed objects is your application really creating?

Over time, these boxing operations can add up to have a significant impact on performance. When I ran the code in Listing 3 over the strongly typed implementation I was able to execute the loop 2.3M times per second. How-ever, the loosely typed implementation got through only .13M times per second. This difference in performance can be explained by looking at the performance statistics in Table 1 (see the side bar .Net Compact Framework Performance Statistics for details on how to interpret these statistics).

As you can see, the difference in amount of work done by the garbage collector varies considerably between the two implementations. In the loosely typed implementation, the garbage collector had to traverse more than 410,000 objects while collecting. The creation of these objects through boxing caused the collector to run four times within our loop. In contrast, the strongly typed implementation didn't create any garbage at all. As a result, the garbage collector never ran.

The Accounts example demonstrates a tradeoff between efficiency and flexibility. I'm not suggesting that you should never design with future extensibility in mind. However, be aware of the performance considerations when evaluating different design alternatives.

String Manipulations
Another situation where managed objects are created when you might not expect them to be is when you're manipulating instances of the System.String class. The implementation of System.String holds an immutable copy of the underlying character string that the type represents. Any changes to the string result in the allocation of a new instance and a copy of the data from the original String instance into the new String instance. As with boxing, applications that perform only a few string manipulations won't see any affect on performance. However, the extra objects created by manipulating strings frequently can add up, just as they did in the boxing example we looked at earlier. Fortunately, there's an easy workaround for applications that need to do frequent string manipulations: use the System.Text.StringBuilder class.

Let's take a look at an example that highlights the differences between using String and StringBuilder when manipulating character strings. The following code sample uses the String concatenation operator to combine several strings together:

String result = "";
for (int i=0; i<10000; i++) {
    result += ".NET Compact Framework";
    result += " Rocks!";
}

This code snippet accomplishes the same task using StringBuilder:

StringBuilder result = ne StringBuilder();
for (int i=0; i<10000; i++){
    result.Append(".NET Compact Framework");
    result.Append(" Rocks!");
}

The performance difference between these two implementations is amazing: the example that uses String takes 173 seconds to run while the StringBuilder version takes only .1 seconds! This dramatic difference can be solely attributed to garbage collection. Look at the performance statistics in Table 2.

As you can see, the garbage collector ran almost 5,000 times in the slower implementation. In addition, by comparing the Bytes Collected by GC and Bytes of String Objects Allocated, you can see that the collector spent almost all of its time collecting "extra" instances of System.String. Of the 173 seconds the loop took to run, 107 of those seconds were spent collecting Strings.

Summary
The single biggest factor affecting the garbage collector's overall impact on application performance is the number of objects your application creates. Because the .Net Compact Framework's garbage collector runs after every 1MB of objects are allocated, the more you allocate, the more time must be spent collecting those objects. In many cases, the objects that are created implicitly on your behalf vastly outnumber the objects you create explicitly using the new operator. The boxing of value types and the extra objects created when changing instances of System.String are two scenarios where implicit object creation can have surprising performance implications. If you suspect a performance problem related to garbage collection, use the .Net Compact Framework performance statistics to find out how often the collector is running, how much time it is taking, and whether most of objects it collects are created implicitly.

SIDEBAR

.NET Compact Framework Performance Statistics
The .Net Compact Framework can be configured to report a number of run-time statistics that describe the performance characteristics of your application. These statistics are enabled by setting the following registry value (a DWORD) to 1:

HKEY_LOCAL_MACHINE\Software\Microsoft\.NetCompactFramework\PerfMonitor\Counters

When the Counters key is set, the .Net Compact Framework creates a file in your application's directory called <application name>.stat. These .stat files contain performance statistics from the various subsystems within the CLR, including the class loader, JIT compiler, and garbage collector.

Throughout this article I'll use performance statistics to highlight the performance differences between the various code examples. In particular, I'll refer to the following statistics:

  • Garbage Collections: The number of times the garbage collector ran.
  • GC Latency Time (ms): The amount of time spent performing collections. In addition to the total time spent collecting, the GC Latency Time counter also reports the minimum, maximum, and average collection latencies.
  • Boxed Value Types: The number of value types that were boxed during the lifetime of the application.
  • Managed String Objects Allocated: The number of instances of System.String that were created while the application ran.
  • Bytes Collected By GC: The number of bytes collected while the garbage collector ran. Like the Latency Time counter, the Bytes Collected counter reports total, minimum, maximum, and average number of bytes collected across all runs of the garbage collector.
More information on the .Net Compact Framework performance statistics, including a full description of all data values, can be found in the .Net Framework SDK documentation or on David Kline's blog (http://blogs.msdn.com/davidklinems/archive/2005/12/09/502125.aspx ).

More Stories By Steven Pratschner

Steven Pratschner is the program manager for the .Net Compact Framework Common Language Runtime at Microsoft. Before working on the Compact Framework team, Steven spent several years working on the full .Net Framework. Steven has written articles and presented at numerous conferences on a variety of topics related to .Net-based programming. He is the author of the book Customizing the Common Language Runtime from Microsoft Press.

Comments (1) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
SYS-CON Australia News Desk 02/19/06 01:33:50 PM EST

Perhaps the most commonly asked questions regarding memory management in .NET are: 'How long does a garbage collection take,' and 'How can I control when the garbage collector runs?' Apprehensive that 'pauses' caused by garbage collections will be perceived by users, application developers often search for ways to control when garbage collections occur. Not surprisingly, the standard answer from Microsoft on these issues is to leave the collector to do its thing instead of trying to control it manually. Nevertheless, concern over garbage collection timing and performance remains.