At least when it comes to instantiating objects.
Even in today’s environment, when the typical amount of RAM on each server is in the gigabytes, it’s still wise to pay attention to memory usage. As a developer or architect, you need to be aware of the trade-offs between eager instantiation and lazy instantiation. Yes, it’s rather pointless to consider an Int16 versus an Int32 for a variable if it’s just going to be created and used a few times in the lifetime of your application. However, if that same variable is instantiated thousands of times or more, then the potential improvement in either memory usage or performance (whichever is more important to you) is definitely worth a look.
Eager/Lazy Instantiation Defined
With eager instantiation, the object is created as soon as possible:
Example – Eager Instantiation
public class Customer
{
// eager instantiation
private Address homeAddress = new Address();
public Address HomeAddress
{
get
{
return homeAddress;
}
}
}
With lazy instantiation, the object is created as late as possible:
Example – Lazy Instantiation
public class Customer
{
private Address homeAddress;
public Address HomeAddress
{
get
{
// Create homeAddress if it’s not already created
if (homeAddress == null)
{
homeAddress = new Address();
}
return homeAddress;
}
}
}
Eager/lazy instantiation also applies to classes, singletons, etc. The principles and potential advantages/disadvantages are similar. For this article, I am only discussing the instantiation of class members.
CPU Cycles vs. Memory Usage
Eager vs. lazy instantiation is the classic performance/memory trade-off. With eager instantiation, you gain some performance improvement at the cost of system memory. Exactly what kind of performance/memory trade-off are we talking about? The answer depends mostly on the objects themselves:
- How many instances of the parent object do you need?
- What is the memory footprint of the member object?
- How much time does it take to instantiate the member object?
- How often will the parent object/member object be accessed?
Calculating the Memory Footprint of an Object
According to my own experiments (using DevPartner Studio and .NET Memory Profiler), each reference-type object (class) has a minimum memory footprint of 12 bytes. To calculate the total memory footprint of each reference-type object, add up any other memory used by members in the object. To get the exact memory footprint, you also need to take into consideration “boundaries” but for our purpose that’s probably not important.
The memory foot-print of an object can be closely approximated using the following table (from MSDN Magazine):
Type | Managed Size in Bytes |
System.Boolean | 1 |
System.Byte | 1 |
System.Char | 2 |
System.Decimal | 16 |
System.Double | 8 |
System.Single | 4 |
System.Int16 | 2 |
System.Int32 | 4 |
System.Int64 | 8 |
System.SByte | 1 |
System.UInt16 | 2 |
System.UInt32 | 4 |
System.UInt64 | 8 |
Using the example Customer class above, let’s say that each Address object take up 1 KByte, and my application frequently needs to instantiate up to 10,000 Customer objects. Just by creating 10,000 Customer objects, we would need about 10 Megabytes of memory. Now let’s say that the HomeAddress member is only needed when the user drills down into the details of a Customer, and we are looking at a potential saving of 10 Megabytes of memory by using lazy instantiation on HomeAddress.
Memory Usage Can Also Impact Performance
Another important consideration with .NET managed code is garbage collection. In .NET managed code, memory usage has a hidden impact on performance in terms of the work the garbage collector has to perform to recover memory. The more memory you allocate and throw away, the more CPU cycles the garbage collector has to go through.
Recommendations
- Pay closer attention to classes that get instantiated multiple times, such as Orders, OrderItems, etc.
- For light-weight objects, or if you are not sure, use lazy instantiation.
- If a member object is only used some of the times, use lazy instantiation.
Additional Reading
Rediscover the Lost Art of Memory Optimization in Your Managed Code