[.net] How do I improve memory usage in C# ?

Started by
19 comments, last by Holy Fuzz 18 years, 9 months ago
Hi, After my first game in C#, I've notice the memory problem. Anytime I click on the New game button, the memory using increased (I use Task manager to track) and the Mem usage column always show the same value as in Peak Mem Usage. That's not normal, isn't it? Well, after i implement IDisposable for every game object, the amount of memory increased is not as much as before, but the Mem Usage keeps topping at the Peak Mem Usage, I don't know why. I can keep pushing New Game and see Mem Peak and Mem Usage both goes up to 100MB or more, but they don't fall down. How do I improve that?
:: Try not. Do. Or do not. There is no try. :: (Yoda)http://80.100.152.172/nguyenkhanhduy
Advertisement
Check out the Clr Profiler. It will let you track where your memory allocations are coming from.

Implementing IDisposable won't actually gain you anything. And if you implement a finalizer, it will actually delay the garbage collection of your objects, since they will have to spend one collection in the finalizer queue.
IDisposable allow you to control more easily the memory usage. But if you implement it, you must call Dispose() on every object that implments it. Otherwise you wont gain anything as sjelkjd said, objects that have a finalizer that is not called by code will survive at least 2 pass of GC, which will lead to more object being promoted to generation 2 and make memory usage even worse. But as long as you call Dispose() on every object you should be fine.
You should NOT be using IDisposable to free up memory - it is meant to be used with unmanaged resources such as window handles, file streams, etc. Think about it - pretty much the only useful thing you could do is set all references in the class to null, so they'll be collected during the next pass. However, if you're disposing your object, it you shouldn't be holding on to a reference to it, since the object will be invalid. And if there is no reference to it, then the GC will collect it during the next pass - and it will also collect any objects in the class. So implementing IDisposable to try to handle managed memory is a waste of time.

Also, you should try to find out the frequency of garbage collections. You can check this with the performance monitors (control panel - admin tools - performance). You can add a counter for garbage collections(in the .net clr memory category), and look at % time in GC. This should ideally be no greater than about 15% for your application. If it is much higher, then something is wrong - you're thrashing the memory manager. Rico Mariani(CLR perf architect) calls this Mid life crisis.
Thanks for your supports.

I understood the use of Dispose() and Finalize(). What I did in my game was implement IDisposable for every object that has a mesh, and call its Dispose() in the "game object container". I thought Mesh, Device, AudioPlayer etc... all have unmanaged resources bundled with them, so...


Maybe I'll check over all the source code again.
:: Try not. Do. Or do not. There is no try. :: (Yoda)http://80.100.152.172/nguyenkhanhduy
Quote:Original post by vincent_valentine
Thanks for your supports.

I understood the use of Dispose() and Finalize(). What I did in my game was implement IDisposable for every object that has a mesh, and call its Dispose() in the "game object container". I thought Mesh, Device, AudioPlayer etc... all have unmanaged resources bundled with them, so...


Maybe I'll check over all the source code again.


Well, it sounds like that part is working as it should. Have you used CLRProfiler? It gives you a ton of very useful information for tracking down memory usage.
Yes they all have unmanaged resources associated with them. But their Dispose() methods will all be called during finalization, so if you don't care about when they are disposed, you don't have to worry about it.

That being said, watch out for when DirectX references something even though you don't. For example, the Direct3D device references all resources (or maybe only managed resources) so that it can maintain them internally. What this means is that even if YOU don't reference them, D3D still does, and they won't be disposed until the D3D device is disposed. So yes, if you want to destroy these sooner, you'll have to call dispose yourself on them. This is a problem that acounted for a huge memory leak in my application that took me forevery to fix, so watch out.

Also note that the CLR profiler only shows you managed memory usage, not unmanaged. And while the D3D resources are probably contained within managed wrappers, the amount of memory actually being used by one of those wrappers is going to be much higher than what the profiler claims.
Quote:Original post by Holy Fuzz
What this means is that even if YOU don't reference them, D3D still does, and they won't be disposed until the D3D device is disposed. So yes, if you want to destroy these sooner, you'll have to call dispose yourself on them. This is a problem that acounted for a huge memory leak in my application that took me forevery to fix, so watch out.


Holy Fuzz, can you explain it more clearly? This seems like an interesting experience...


In my game app, I've tried not to call Dispose() manually, and the memory effect remains the same. So they're called automatically anyway, true.

Anyway, here's the link to the post to download my game, it's called Tetris#
Link to my game

[Edited by - vincent_valentine on July 6, 2005 5:58:24 AM]
:: Try not. Do. Or do not. There is no try. :: (Yoda)http://80.100.152.172/nguyenkhanhduy
Okay, so here's my best guess as to what's happening (if anyone has any other ideas or knows for sure, then please say so)...

The memory for a .Net application is divided into two sections: one for managed resources (your regular old objects) and one for unmanaged resources.

Now, DirectX is an *unmanaged* API, and hence all of the resources it creates are allocated into the section for unmanaged resources. This includes things like DX-managed (not to be confused with .Net-managed) textures and vertex buffers, which are stored in system memory alongside video memory so that when the device is lost DX can automatically restore them without any hastle.

"Managed DirectX" is merely a .Net wrapper around the normal, unmanaged DX. What this means is that when you create a texture in managed DX, most of the memory allocated for it is actually unmanaged, and only a small fraction (the wrapper itself) is managed.

Now, as you already know, when a wrapper to a DX resource such as a texture is garbage collected (or we call Dispose), the wrapper also releases the unmanaged resources associated with it.

But this doesn't tell us why your app is using so much memory...

One possibility (I am not sure about this, so maybe someone could confirm?) is that the managed-DX wrapper itself contains references to the managed wrappers around the various unmanaged resources (textures, vertex buffers, etc...), which means that NONE of the resources will be garbage collected (and disposed) until the managed-DX wrapper itself is disposed. Hence, if this is the case, none of your resources will be released until either you manually dispose them or the DX device itself is disposed.

The other possibility (and I'm pretty certain that this is happening) has to do with how the garbage collector decides to collect unreferenced objects. If you don't already know, all of your managed objects are divided into 3 generations. All newly created objects are created in gen 0. When your app runs out of memory, it collects all gen 0 objects that are unreferenced, and if afterwards it has enough available memory to continue without further garbage collections of any higher generations, it simply stops. Any object which survives a gen 0 collection is moved to gen 1, and so on...

Now, note that the garbage collector only keeps track of *managed* memory usage, and not unmanaged memory usage. This means that large allocations of unmanaged memory will NOT trigger garbage collections, and the memory allocated by the managed wrappers around those resources is so small that they too very infrequently trigger garbage collections. Also, these resources are very likely to be promoted to gen 1 or even gen 2, meaning that gen 0 collections won't reach them, and thus that they will not be disposed and WILL NOT RELEASE THEIR UNMANAGED RESOURCES. Since the garbage collector does not keep track of unmanaged resources, allocation of texture and vertex buffers appear to the GC to have a much smaller memory footprint than they actually do.

Please keep in mind that both of the possibilities I arrived at via my own experience and relatively limited knowledge about the CLR. If someone knows better than I, please speak up.

- Fuzz
You know, without measuring, guessing is not really going to take you very far. You should measure your memory allocations, and see where it's coming from. If it's not showing up as managed allocations, then you should look at unmanaged resources. But just guessing without data is a waste of time.

This topic is closed to new replies.

Advertisement