| By Steven Pratschner | Article Rating: |
|
| February 19, 2006 12:30 PM EST | Reads: |
14,431 |
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.
The single biggest factor in determining when a garbage collection occurs and how long it will take is the number of managed objects that your application allocates. Therefore understanding how many objects you allocate is the best way to determine how your application will be affected by garbage collection performance. As shown in Figure 1, the GC latency, or the time spent in garbage collection, is directly related to the number of live objects allocated by the application.
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
As you likely know, the .NET-type system defines two different kinds of types: value types and reference types. Value types provide an efficient means to create and work with simple, frequently used types. Many of .NET's built-in types such as Int32, as well as enums and any type defined with the struct keyword in C# (Structure in Visual Basic.NET) are value types. Reference types are those types that are created with the new keyword. The most significant difference between value types and reference types as far as memory management is concerned is that value types are allocated on the stack while reference types are allocated on the garbage collector's heap.
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();
a.Add(200);
generates the following IL for the call to ArrayList.Add:
ldc.i4 0xc8
box [mscorlib]System.Int32
callvirt instance int32
[mscorlib]System.Collections.ArrayList:: Add(object)
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:
- AccountId: Account identifiers are represented by the AccountId value type. AccountId contains an integer to hold the Account number and provides an implementation of GetHashCode. In our example, the hash code is simply the Account number. The symmetry between the hash code and the Account number makes for a perfect hash function.
- AccountData: Each Account has both an Account identifier and an Account balance. The Account balance is held in the AccountData value type.
- Accounts: The Accounts reference type holds a collection of 10,000 AccountData records. Accounts also provides an index operator that allows a consumer to access the AccountData for a given AccountId.
- The base type of AccountData: The code in Listing 1 allows alternative implementations of AccountData to be provided in the future. This flexibility is provided by defining an interface of type IAccountData from which the AccountData value type derives. The implementation of AccountData in Listing 2 does not derive from such an interface.
- The type of the accounts array: Because the implementation in Listing 1 allows for alternate implementations of an account's data, the type of the accounts array is IAccountData. The type of the accounts array in the strongly typed implementation is simply AccountData.
- The return type and the "parameter" to the index operator: The fact that the type of the accounts array is a reference type (IAccountData) in Listing 1 requires that a reference type be the return type and parameter to the index operator. In our case, the index operator works with instances of System.Object.
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;
Published February 19, 2006 Reads 14,431
Copyright © 2006 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
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.
![]() |
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. |
||||
- Kindle 2 vs Nook
- Practical Approaches for Optimizing Website Performance
- SQL Anywhere Server and AJAX
- PowerBuilder Top Feature Picks
- The Difference Between Web Hosting and Cloud Computing
- PowerBuilder 12 and .NET
- Contrary Opinion: Why Silverlight is Good for Adobe
- Ajax in RichFaces 3.3, JSF 2 and RichFaces 4
- Wave on Ulitzer: Confessions of a Google Wave Fanboy
- Cloud Computing Best Practices
- AJAX World RIA Conference & Expo Kicks Off in New York City
- Rich Content Rotator for ASP.NET
- RIAs for Web 3.0 Using the Microsoft Platform
- Kindle 2 vs Nook
- Practical Approaches for Optimizing Website Performance
- Social Media Terrorists
- SQL Anywhere Server and AJAX
- SYS-CON's Cloud Expo Adds Two New Tracks
- PowerBuilder Top Feature Picks
- The Difference Between Web Hosting and Cloud Computing
- Google Maps and ASP.NET
- Crystal Reports XI & How It Has Changed
- Converting VB6 to VB.NET, Part I
- Creating Controls for.NET Compact Framework in Visual Studio 2005
- Where Are RIA Technologies Headed in 2008?
- How to Write High-Performance C# Code
- AJAX World RIA Conference & Expo Kicks Off in New York City
- Implementing Tab Navigation with ASP.NET 2.0
- i-Technology Photo Exclusive: Bill Gates & Steve Jobs In "Nerds"
- .NET Archives: Getting Reacquainted with the Father of C#

































