views:

191

answers:

4

I am writing an application in C# that makes use of a 3rd party COM DLL, this dll creates a lot of resources (like bitmaps, video, data structures) in unmanaged memory. While digging around I came across the following call for the Garbage Collector:

GC.AddMemoryPressure(long long bytesAllocated)

It is documented in MSDN here:

http://msdn.microsoft.com/en-us/library/system.gc.addmemorypressure.aspx

This sounds like something I should be calling since this external dll is createing a lot of resources the CLR is unaware of.

I guess I have two questions...

  1. How do I know how much memory pressure to add when the dll is 3rd party and it's not possible for me to know exactly how much memory this dll is allocating.
  2. How important is it to do this?
+2  A: 

It is not important for you to do this, unless you're noticing memory issues that you believe are caused by the GC.

SLaks
+1  A: 

The most important thing you can do is call the Dispose() method on these third-party DLL classes, and be absolutely fanatical about doing it. (Or, of course, using "using" blocks, since they call Dispose() automatically on exit from the block.)

The thing about unmanaged resources is that .NET classes that wrap or use them need to hava finalizer, as well as implementing IDisposable() pattern. If Dispose() is called, it should take care of the unmanaged resources immediately, and it should also suppress the finalizer. If you don't call Dispose(), then you have real memory issues, and adding "pressure" to the garbage collector won't fix it.

The underlying logic of the garbage collector is that any class that has an active finalizer isn't dealt with until all other possibilities are already exhausted. That means gen0, gen1, and gen2 cleanup have already occurred. A finalizer that hasn't been suppressed is almost as bad as a memory leak.

So call Dispose(). Make sure you never miss it.

EDIT: Just noticed that the 3rd party DLL is a COM DLL. Best practice is to make sure that your .NET wrappers implement IDisposable() fully (not just providing a Dispose method). Then make sure that you're always calling Dispose() when you're done with the COM objects.

Cylon Cat
Thanks for the advice, yeah I am pretty fanatical about using Dispose so it sounds like I should be good. Thanks again!
Steve Sheldon
What should the wrapper class do when `Dispose` is called on it? (I've updated my answer with the answer to that!)
Daniel Earwicker
+2  A: 

In any mixed native/managed process, there is a mixture of native/managed memory usage. If there is no GC-controlled relationship between the two, then there would be no need for this API. For example, if there are certain deterministic state changes in the managed code that cause native memory to be allocated and deallocated, then nothing the GC can do will ever force native memory to be released.

However, very often there is native memory held by managed objects that have finalizers. So the GC can reduce the size of the native heap, simply by triggering a collection and getting those finalizers to run.

Therefore if you have a lot of that going on, it could well be necessary to call this API (just like the documentation says).

As for how much you should tell it, that's probably not something you can cook up an answer for by pure analysis, especially with a 3rd party library. You need to run Performance Monitor, run a test that allocates a lot of the 3rd party objects, and look at the Native Bytes and CLR memory counters to see how they relate.

As you're using a COM object, you could in fact deterministically force instances to clean up when you know you no longer need them, by using Marshal.ReleaseComObject. Note that you need to use a goofy loop to make it get rid of the object:

while (Marshal.ReleaseComObject(obj) != 0) 
{
}
Daniel Earwicker
OK, cool so manually tell the Runtime Callable Wrapper to release all its references (while being really careful not to have any stray pointers lying around that could start throwing System.NullReferenceException's). On the memory usage, I looked at performance counters but depending on the size of video the memory can change, so that didn't help much. I was hoping to find a way to figure this out dynamically ... this dll can be in use for a while so it seems like it would make sense to let the GC know that theres a memory elephant in the background.
Steve Sheldon
NB. if you consistently force the COM objects to shutdown in a deterministic way, you are then in the situation I describe in my first paragraph: the GC will only need to be responsible for memory usage that it can directly detect, and so you don't need to call the memory pressure APIs.
Daniel Earwicker
To look at it another way, the .NET GC never, ever manages unmanaged objects. That's the developer's responsibility, and IDisposable pattern is how you do it. So there's no setting you can influence on GC to make it do a better job of cleaning up unmanaged objects.
Cylon Cat
@Cylon Cat - the GC can indirectly manage any resource, via finalizers (or more preferably safe handles). It only directly manages memory. The problem is the word "unmanaged" which has a lot of baggage and is used with a different meaning in almost every context in which it appears.
Daniel Earwicker
@Daniel, in the context of the .NET GC, "unmanaged" means exactly one thing: any code or memory allocations not based on .NET. It's also incorrect to say that GC manages anything through finalizers; finalizers are not part of the GC. They're left completely up to the application programmer to write them, or not write them, as the case may be. It's also clear from the GC processing algorithms that the GC will touch finalizers only as a last resort.
Cylon Cat
The GC determines when a finalizer runs by pushing unreferenced objects onto the finalization queue. A finalizer is a way to hook some custom code into the GC, to cause the GC to *indirectly* manage the state of some resource that would otherwise need to be managed manually. (Why do people seem to miss the words "directly" and "indirectly" in my comments?)
Daniel Earwicker
+1  A: 

Let's say I have an object like this:

public class SomeImageType : IDisposable
{
    public int Width { get; private set; }
    public int Height { get; private set; }
    public PixelFormat PixelFormat { get; private set; }
    IntPtr ImageData { get; private set; }
    // implementation of constructor and IDisposable not shown
}

How much memory does this take? 20 bytes + object overhead? When it comes time for collection, if this object has no references it makes no difference at all. But does it take that 20 bytes? Hell no - it's an image. So take that 20 and multiply it by the width, the height and the number of bytes per pixel and you have something that takes up (possibly) megabytes. By telling the GC to add memory pressure, you are saying that there is an iceberg in there chewing up resources. When you dispose the object, you will, of course, free that memory and tell the GC to release that pressure. These two calls let you hint to the GC that there are strange things afoot at the Circle K, and maybe it would like to schedule collection differently.

plinth