|
YOUR FEEDBACK
|
TOP MICROSOFT .NET LINKS Managed Space Cover Story: Garbage Collection
How many managed objects is your application really creating?
Feb. 19, 2006 12:30 PM
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.
At first glance, determining how many objects your application creates appears easy: because you create the objects yourself using the new operator, it's obvious when new objects are created, right? Unfortunately, tracking the number of newly created objects isn't nearly that straightforward because objects are created "under the covers" on your behalf in response to certain operations. In many cases, the objects created implicitly vastly outnumber the objects you explicitly create. Furthermore, it's not always obvious to ascertain when objects are being implicitly created simply by looking at source code. This article helps you understand how the garbage collector will affect your application's performance by pointing out the "not so obvious" situations where managed objects are created on your behalf. In particular we'll look at the effects of boxing and string manipulations on the number of objects created as your application runs. Although this article is written specifically with the .Net Compact Framework in mind, many of the concepts discussed apply to the full .Net Framework as well.
Boxed Value Types There are many convenience types defined in the .NET class libraries that are designed to work with both value types and reference types. Examples of such types include collections such as array lists and hashtables. In order to work well with any type, collections like these often include methods that take instances of System.Object as a parameter. As an example, consider the definition of the Add method on System.Collections.ArrayList: public virtual int Add(object value); Because Object sits at the top of the .NET inheritance hierarchy, any type, regardless of whether it is a value type or a reference type, can be passed at run time. Loosely typed collections such as these are great for programmer productivity, but have surprising performance implications in some scenarios. When a value type is passed as a parameter to a method defined to take a reference type, the CLR will automatically convert the value type to a reference type at run time. This conversion, termed boxing, involves two steps: memory is allocated on the GC heap for the new "reference type," and the contents of the value type are copied into the newly allocated space. In Microsoft Intermediate Language (IL), boxing operations can be identified by the box instruction. For example, the following C# code:
ArrayList a = new ArrayList(); generates the following IL for the call to ArrayList.Add:
ldc.i4 0xc8 As you see, this code boxes an instance of System.Int32 before calling Add. In applications where boxing occurs infrequently, its affect on performance is negligible. However, because the .Net Compact Framework's garbage collector initiates a collection whenever 1MB of objects have been allocated, scenarios in which a significant amount of boxing occurs can cause the garbage collector to run more frequently than it would have to. Not only may the collector run more often, but the time spent in each collection may increase due to the length of time it takes for the garbage collector to examine the entire object graph. Let's take a look at an example that demonstrates the potential affects of boxing on performance and garbage collection frequency. Credit for this sample goes to Roman Batoukov from the .Net Compact Framework team. Roman developed this sample while preparing for breakout sessions at conferences, including the Mobile and Embedded Developers Conference (MEDC) and the Professional Developers Conference (PDC). Roman's sample consists of three types that form a rudimentary banking system. Two implementations of these types are provided: a strongly typed implementation that involves almost no boxing, and a loosely typed implementation that provides more flexibility, but incurs the cost of several boxing operations at run time. The types involved in the sample are:
Now that we've looked at the differences between the two implementations, let's see how they perform. The code in Listing 3 iterates through the accounts collection, initializing each account with a balance of 100. After all accounts have been populated, we iterate back through them, deducting 10 from each account. The loop that updates the balances is timed using Environment.TickCount. I iterate through the loop long enough to run the entire operation for at least 1 second (generally speaking, running a performance test for at least one second increases the consistency of the results). There are several places where boxing occurs when running the code in Listing 3 against the loosely typed implementation of our Account class. The first boxing operation occurs when we pass an instance of our AccountId value type to the index operator that takes an instance of Object: AccountData rec = (AccountData)ac[id]; The same boxing operation occurs two lines later in our listing when we use the index operator again: ac[id] = rec; Finally, the modified instance of the AccountData value type must be boxed when it is stored back in the collection: ac[id] = rec; MICROSOFT .NET LATEST STORIES
SUBSCRIBE TO THE WORLD'S MOST POWERFUL NEWSLETTERS SUBSCRIBE TO OUR RSS FEEDS & GET YOUR SYS-CON NEWS LIVE!
|
SYS-CON FEATURED WHITEPAPERS MOST READ THIS WEEK BREAKING NEWS FROM THE WIRES
|
|||||||||||||||||||||||||||||||||||