Ayende @ Rahien

Refunds available at head office

Patterns for reducing memory usage

Memory problems happen when you application use more memory that you would like. It isn’t necessarily paging or causing OutOfMemory, but it is using enough memory to generate complaints. The most common cases for memory issues are:

  • Memory leaks
  • Garbage spewers
  • In memory nuts
  • Framework bugs

Let me take each of them in turn.

Memory leaks in a managed language are almost always related to dangling references, such as in a cache with no expiration or events where you never unsubscribe. Those are usually nasty to figure out, because tracking down what is holding the memory can be unpleasant. But, by the same token, it is also fairly straightforward to do so.

Garbage spewers are pieces of code that allocate a lot of memory that will have to be freed soon afterward. A common case of that is:

public string Concat(string[] items)
{
   string result = "";
   foreach(var item in items)
      results += item;
 
   return result;
}

This is going to allocate a lot of memory, which will have to be freed soon after. This will get cleaned up eventually, but it will put a lot of pressure on the GC first, will cause the application to consume more memory and in general won’t play nice with others. While the code above is the simplest way to explain this, it is fairly common in ways that are harder to detect, a common case would be to load a DTO from the database, convert that to an entity and convert that to a view model. Along the way, you are going to consume a lot of memory for doing pretty much the same thing.

Now the caveat here is that most objects are actually small, so you don’t really notice that, but if you are working with large objects, or a lot of them, this is something that is going to hit you.

In memory nuts refer to a common problem, you simply put your entire dataset in memory, and commonly refer to it by direct model traversal. When your dataset becomes too big, however… well, that is the point where the pain is really going to hit you. Usually, fixing this is a costly process, because your code assumes that the entire thing is in memory. Even if you can easily save it to persistent storage, fixing all the places where the code assumes that everything is just a pointer reference away is a big problem.

Framework bugs are my least favorite, it is when you run into cases where the framework just won’t release memory. Most often, this is because you are doing something wrong, but occasionally you will hit the real framework bug, and tracking that down is a pure PITA.

In all cases, you need to set up some goals, what is acceptable memory usage, in what scenarios, over what time frame, etc. Then build test scenarios that are repeatable and try each of your improvements out. Do not try to implement too much upfront, that way lies the road to madness.

Comments

Pet
01/03/2010 05:37 AM by
Pet

Really interesting article.

But, I did some test and found that Code sample above posted doesn't create garbage in the foreach loop.

string gets finalized only when we return from the function.

Please help with this example.

Ayende Rahien
01/03/2010 06:43 AM by
Ayende Rahien

Pet,

String is immutable, as such, a new string is generated every time we concat the string.

Fero
01/03/2010 08:59 AM by
Fero

Garbage spewers (quick fix)

public string Concat(string[] items)

        {

            StringBuilder sb = new StringBuilder();

            foreach (var item in items)

            {

                sb.Append(item);

            }

            return sb.ToString(); 

        }
Ayende Rahien
01/03/2010 09:06 AM by
Ayende Rahien

Fero,

I am not really worried about that particular example, I am talking about the general case.

Jon
01/04/2010 08:11 PM by
Jon

Minor code fix: "results" should be "result" in your garbage spewer example. Threw me off for a second.

Kevin Dente
01/05/2010 05:35 PM by
Kevin Dente

Another fun one is the scenario with small managed objects managing large unmanaged objects. COM interop is the primary example. When you have large COM objects being used from managed code, the garbage collector isn't aware of that memory usage and doesn't trigger garbage collection.

Ayende Rahien
01/05/2010 06:43 PM by
Ayende Rahien

Kevin,

That is why you have GC.AddMemoryPressure

Kevin Dente
01/05/2010 06:52 PM by
Kevin Dente

Sure, but AddMemoryPressure assumes a priori knowledge of the size of the objects. Not very practical in the general case. It's one of the nasty flaws in the whole COM interop story.

Comments have been closed on this topic.