.NET Framework 2.0 Performance Inspection Questions - Memory Management

From Guidance Share

Revision as of 05:02, 21 December 2007; JD (Talk | contribs)
(diff) ←Older revision | Current revision | Newer revision→ (diff)
Jump to: navigation, search

- J.D. Meier, Srinath Vasireddy, Ashish Babbar, Rico Mariani, and Alex Mackman


Contents

Do You Manage Memory Efficiently?

To identify how efficiently your code manages memory, review the following questions:


Do you call Dispose or Close?

Check that your code calls Dispose or Close on all classes that support these methods. Common disposable resources include the following:

  • Database-related classes: Connection, DataReader, and Transaction.
  • File-based classes: FileStream and BinaryWriter.
  • Stream-based classes: StreamReader, TextReader, TextWriter, BinaryReader, and TextWriter.
  • Network-based classes: Socket, UdpClient, and TcpClient.


Also check that your C# code uses the using statement to ensure that Dispose is called. If you have Visual Basic .NET code, make sure it uses a Finally block to ensure that resources are released.


Do you have complex object graphs?

Analyze your class and structure design and identify those that contain many references to other objects. These result in complex object graphs at runtime, which can be expensive to allocate and create additional work for the garbage collector. Identify opportunities to simplify these structures. Simpler graphs have superior heap locality and they are easier to maintain.

Another common problem to look out for is referencing short-lived objects from long-lived objects. Doing so increases the likelihood of short-lived objects being promoted from generation 0, which increases the burden on the garbage collector. This often happens when you allocate a new object and then assign it to a class level object reference.


Do you set member variables to null before long-running calls?

Identify potentially long-running method calls. Check that you set any class-level member variables that you do not require after the call to null before making the call. This enables those objects to be garbage collected while the call is executing.


Note There is no need to explicitly set local variables to null because the just-in-time (JIT) compiler can statically determine that the variable is no longer referenced.


Do you cache data using WeakReference objects?

Look at where your code caches objects to see if there is an opportunity to use weak references. Weak references are suitable for medium- to large-sized objects stored in a collection. They are not appropriate for very small objects.

By using weak references, cached objects can be resurrected easily if needed or they can be released by garbage collection when there is memory pressure.

Using weak references is just one way of implementing caching policy. For more information about caching, see "Caching" in Chapter 3, "Design Guidelines for Application Performance."

Do you call ReleaseComObject?

If you create and destroy COM objects on a per-request basis under load, consider calling ReleaseComObject. Calling ReleaseComObject releases references to the underlying COM object more quickly than if you rely on finalization. For example, if you call COM components from ASP.NET, consider calling ReleaseComObject. If you call COM components hosted in COM+ from managed code, consider calling ReleaseComObject. If you are calling a serviced component that wraps a COM component, you should implement Dispose in your serviced component, and your Dispose method should call ReleaseComObject. The caller code should call your serviced component's Dispose method.


Do You Call GC.Collect?

Check that your code does not call GC.Collect explicitly. The garbage collector is self-tuning. By programmatically forcing a collection with this method, the chances are you hinder rather than improve performance.

The garbage collector gains its efficiency by adopting a lazy approach to collection and delaying garbage collection until it is needed.


Do You Use Finalizers?

Finalization has an impact on performance. Objects that need finalization must necessarily survive at least one more garbage collection than they otherwise would; therefore, they tend to get promoted to older generations.

As a design consideration, you should wrap unmanaged resources in a separate class and implement a finalizer on this class. This class should not reference any managed object. For example, if you have a class that references managed and unmanaged resources, wrap the unmanaged resources in a separate class with a finalizer and make that class a member of the outer class. The outer class should not have a finalizer.

Identify which of your classes implement finalizers and consider the following questions:


Does your class need a finalizer?

Only implement a finalizer for objects that hold unmanaged resources across calls. Avoid implementing a finalizer on classes that do not require it because it adds load to the finalizer thread as well as the garbage collector.


Does your class implement IDisposable?

Check that any class that provides a finalizer also implements IDisposable, using the Dispose pattern described in .NET 2.0 Performance Guidelines - Finalize and Dispose.


Does your Dispose implementation suppress finalization?

Check that your Dispose method calls GC.SuppressFinalization. GC.SuppressFinalization instructs the runtime to not call Finalize on your object because the cleanup has already been performed.


Can your Dispose method be safely called multiple times?

Check that clients can call Dispose multiple times without causing exceptions. Check that your code throws an ObjectDisposedException exception from methods (other than Dispose) if they are invoked after calling Dispose.


Does your Dispose method call base class Dispose methods?

If your class inherits from a disposable class, make sure that it calls the base class's Dispose.


Does your Dispose method call Dispose on class members?

If you have any member variables that are disposable objects, they too should be disposed.


Is your finalizer code simple?

Check that your finalizer code simply releases resources and does not perform more complex operations. Anything else adds overhead to the dedicated finalizer thread which can result in blocking.


Is your cleanup code thread safe?

For your thread safe types, make sure that your cleanup code is also thread safe. You need to do this to synchronize your cleanup code in the case where multiple client threads call Dispose at the same time.


Do You Use Unmanaged Resources Across Calls?

Check that any class that uses an unmanaged resource, such as a database connection across method calls, implements the IDisposable interface. If the semantics of the object are such that a Close method is more logical than a Dispose method, provide a Close method in addition to Dispose.


Do You Use Buffers for I/O Operations?

If your code performs I/O or long-running calls that require pinned memory, investigate where in your code the buffers are allocated. You can help reduce heap fragmentation by allocating them when your application starts. This increases the likelihood that they end up together in generation 2, where the cost of the pin is largely eliminated. You should also consider reusing and pooling the buffers for efficiency.

Personal tools