-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v3.5.1+ ContentManager.Unload() doesn't seem to free up memory #5341
Comments
ContentManager.Unload() should dispose all loaded assets and free up any native memory they are using. This will not free up managed memory as that is only freed when a garbage collection is done. What memory metric are you looking at here? |
Here's a thread I posted about this in the community: http://community.monogame.net/t/3-5-memory-issues-contentmanager-unload-not-working-properly/8463/5
So, to answer your question, I was looking at the Process Memory in VS2015 and Memory Usage in the Windows Resource Monitor. I tested it as thoroughly as I could with 3.5 and 3.6 installed, noticing the same problem in both: memory kept increasing, but never decreased when I unloaded content. 3.4 works fine. |
@gap9269 Is there anything you can share that shows this issue? Maybe even a minimal project that reproduces it?
|
I recorded 3.4 performance to show what it should actually look like. It's awful quality because I recorded it with VLC at like 12fps, but if you watch the list of processes above the game window you can see the memory jump up and down. 3.4: https://www.youtube.com/watch?v=J6lDHuRAYWY In this you can see the memory increase and decrease as I load new areas and quit to the main menu. It always bounces between 90k-250k or so, depending on how much content is loaded. 3.6: https://www.youtube.com/watch?v=muAe7LKd28M I recorded 3.6 with my phone because VLC kept giving me errors. You can see the memory usage only go up, despite doing the same thing as the last video. It doesn't seem to be releasing memory when I unload content (like moving between areas, or quitting to the main menu). I don't know if this helps or not, but it's all I have. Here are the memory profiler reports for 3.4 and 3.6, respectively. It seems like 3.6 is about the same when you look at these. I don't really know what the deal is. |
I've been trying to reproduce this for over an hour now with no success :/ I tried loading and unloading some assets a bunch of times and switched between 3.4 and latest develop, but they pretty much behaved the same. Maybe this is not happening for all types of assets? @gap9269 How are you handling content exactly? Some insight might help in figuring out the issue. Are you using multiple content managers or just one? Are you disposing them or just call unload and reuse? Do you ever manually dispose content? |
Multiple ContentManagers, one for each map and menu. When I leave a map the current map's ContentManager calls UnloadContent(). The next map then loads all of its content. I never manually dispose content, nor do I call Dipose() on anything. It's just Load and Unload. Same for menus. Assets are mostly Texture2Ds |
I bit of an update on this: I'm using 3.6 now and the issue is still constantly rearing its ugly head. I can mostly ignore it until I get an OutOfMemory exception from playing too long. (Note: It's not a time related thing, it's just from loading new areas. Even though I'm calling Unload() on everything it eventually builds up and crashes) I thought that it might be an issue with the higher GC generations not clearing so I'm manually calling GC.Collect() every time I switch areas, but that doesn't fix the problem. Did something with garbage collection change between 3.4 and 3.5? Perhaps it isn't clearing everything that it used to. For now I'll continue to make demo builds of the game using 3.4, but it's a pain to have to switch back to get rid of this issue. |
I'll give this another spin with textures. Maybe it's an issue with GraphicsResources. They're stored in other places than the content manager, though i think only weak references are used and they are even explicitly cleaned up. Did you run into this on multiple platforms? Just to be sure we're not looking in the wrong place. It's weird that not more people are experiencing this. Maybe your code uses MG in some uncommon way that causes the garbage? Are you loading custom types with the content manager? |
I've only tested on PC, but our UWP version is almost running to test on Xbox. I don't think I'm doing anything strange. No custom types are being loaded, the majority is Texture2D. Some videos (.wmv), a couple shaders, some spritefonts. I have a different ContentManager for each area and menu, so I load all of the necessary content on load for those areas, then when I leave them I simply call I can paste larger pieces if there's anything specific that might help. Or we could Skype and screen share to walk through what I'm doing. |
What types of assets are you loading? Would you mind listing all of them? A full profiling log up to the |
Types I'm loading:
To the best of my knowledge I'm not loading anything else. We have about 3GB of assets total. \ Here's a link to a profiling session. I'm not sure if it's exactly what you wanted, I only very recently started dabbling with memory profilers. https://drive.google.com/file/d/0B9OprX_2DQFEMkZEMTRUNDZpQ0U/view?usp=sharing As for keeping references to things, it's totally possible that I'm doing it without realizing it. A lot of this code was written when I was still in school so a lot of it is a mess. What baffles me is that switching back to 3.4 fixes this problem for me. I can swap back to 3.4 and record a profiling session there as well, but I'll wait to see if this is useful in the first place. |
It appears so. Actually 3.5 & 3.6 allocates less memory because of the Scratch Buffer. |
I think it has to be SoundEffect instance. With this code the memory keeps adding up.
If I dispose the sfx then the memory is kept stable. @gap9269 do you load sounds and how do you play them? |
We don't load SoundEffects in the game, we're using Wwise and I simply make API calls to play sounds. It loads everything on its own end. I dug a bit deeper and I'm just getting more and more confused. Here's what I tried: First, I disabled Wwise to see if something was going wrong on its end. There isn't. The only difference was less total memory at the start because it wasn't loading any sounds or making any instances of Sound Objects. Second, I ran the profiler I installed and watched closely as I ran through areas (which Loads and then Unloads them). What I saw was a constant increase in Total Managed Bytes (TMB) and Live Managed Bytes (LMB) every time I loaded assets (these are all Texture2D assets, by the way). I also call a general GC.Collect() on every area Unload. So far this is what I've always seen. However, when I re-entered those areas that I had already loaded, the TMB and LMB stayed the same, despite the fact that I was calling Content.Load on all of their Textures again. Leaving the area still didn't drop the process memory. It seems like maybe the content really isn't being released on Unload for some reason? I'm not sure if that would make it so subsequent Loads wouldn't increase memory usage, but that appears to be what's happening. Here's the kicker: when I quit the game to main menu memory usage doesn't drop. However, when I start a new game I recreate every area class (each has their own ContentManager) to get a clean reset of the game. As soon as I change areas in this new game it calls GC.Collect() and the TMB/LMB/Process Memory plummets down to where I would expect it to be, roughly. It's still a bit high, but this consistently happens and it always drops me down to the same range. (First launch of game is 315k TMB, all reloads afterward are around 340k) It doesn't matter if I get the memory up to over 1GB or if I reload at 300MB, it always resets on that first new GC.Collect(). Maybe the ContentManager is a red herring, but loading/unloading content is the only major thing that happens during area transitions. Short summary: Constant Load/Unload with different ContentManagers only increases usage, doesn't drop it even with manual GC.Collect() calls. Further Load() of the same assets doesn't increase usage. Recreating those ContentManagers and calling GC.Collect() seemingly clears it all out. |
I think I've confirmed that it's a problem stemming from the ContentManagers. I disabled the recreating of the areas (and their ContentManagers) when exiting to the main menu and starting a new game. Now the memory usage no longer drops back down to normal levels on GC.Collect(). Re-loading the same content from the previous playthrough has no effect on memory usage, but entering new areas effects it. Here is how I am creating new ContentManagers in every class that needs one:
Then I simply load content when I enter a map, and here's an example...
(Note: base.LoadContent() just does more of the same. Checks for generic objects and loads their content if present. Nothing special there) Finally, unloading:
The only other thing I can think of that might be wrong is how I handle references to more common textures. What I mean is, some things like NPCs might exist in more than one place and if I unload content their reference to the Texture2D will be disposed, so they won't draw in another area. To get around this I set their Texture2D to be a placeholder texture, then I unload all content. Then when I enter an area with the NPC again I simply set their texture again (npc.sprite = content.Load...). I'm not sure why that would be causing this issue, but I figured I'd mention everything. |
That's the normal behavior of ContentManager, it keeps track of what has been loaded and won't load twice the same asset, unless you load it from a different ContentManager.
There may be something going on here. What's puzzling me is that Texture2D are loaded on the GPU memory and shouldn't impact much the RAM usage. It looks like a content reader issue keeping references to decoded textures. Do you think that you can put together a minimal project exposing the issue? |
Yep! I'm going to try that soon. I have a ton of stuff I need to do today first, but I'm going to get around to it ASAP. I'll post here with a link once I have it. |
Here's a link to the project: https://drive.google.com/file/d/0B9OprX_2DQFENGJwb1FaRHlmdGs/view?usp=sharing It's about 1GB in size because I tossed in a bunch of textures to load. The instructions are pretty simple and the project is minimal, but here's an overview: I created 4 separate ContentManagers and four separate folders of images. By pressing '1', '2', '3', or '4' on your keyboard you will load the corresponding folder with the corresponding ContentManager. Pressing again will Unload it. I use .Net Memory Profiler to test, and I saw the memory usage going up during loads, but never dropping during unloads. I added a final command to fix that. Press 'X' to recreate the ContentManagers and unload everything. For some reason I had to press it twice, but it would clear everything out and return the memory usage to normal. I also switched back to 3.4 to test and saw some crazy things. First, it had much lower TMB/LMB than 3.6 off the start. However, when I loaded content it would run a ton of garbage collections. It was crazy. Unloading -did- drop the memory usage back down though, which is what I expected since I had never had issues with 3.4. Hopefully this helps shed some light on the issue here. |
I bet it was much slower as well! There are two changes between 3.4 and 3.5 for this, First, we avoid full This still has a flaw, which is that the scratch buffer can get as big as the bigger asset you load. |
Ah! That would explain the behavior entirely then. I suppose a quick fix on my end would be to trash and recreate the ContentManager during loading screens, right before I do a manual GC.Collect(). I don't know what this will do to performance (if anything, really), but it should prevent future OutOfMemory exceptions. Unfortunately, that won't help anyone who runs into this the way I did. Is it possible to drop the size of the scratch buffer down after the ContentManager is unloaded? Regardless, this issue might be worth closing now since it is a little misleading. What are your thoughts? |
I don't see a reason to do so since the scratch buffer will grow again with the next assets. This will defeat the purpose of the scratch buffer and just make your loading slower.
True. Although it's a fraction of the total memory it's still something that MG can improve. One issue is that each ContentManager has at least 1MB of scratch memory, this could be a problem when someone uses multiple ContentManagers, there are scenarios where you have one manager per asset, for example when you want to have independent instances and manipulate assets with SetData() or handle the life of those assets more precisely.
The other issue is that the buffer grows to the largest asset. Thoughts? |
@gap9269 How does this cause the OutOfMemoryException? The number of ContentManagers you have doesn't keep growing does it? The scratchbuffer will be GC'ed when its ContentManager is, so I don't get why memory keeps going up in your game.
We already have a ByteBufferPool class, so this wouldn't be too hard. It might need some minor adaptations like a minimum array size. Maybe we could offer a function to clear the pool to reduce ram usage when not loading assets. That could be done for the scratchbuffer as it is right now too, offer a function to discard it to clear memory.
I think there wouldn't be any issues with compressed formats. |
Right, I think I'm seeing the issue a bit more clearly now. This particular issue that I'm having is the result of two things:
These things together obviously create an issue over time. It seems like limiting the amount of ContentManagers in a project will prevent any major problems, but it is a bit difficult considering you can't choose to unload single assets. I can't think of a way to get around not having a specific ContentManager for menus, gameplay, etc if you want to be as efficient as possible with what assets you have loaded. Even if you have a small handful of ContentManagers, you could still run into issues if you load giant assets for each one. Loading assets in smaller chunks would certainly help this problem.
We have a static amount of ContentManagers, but they aren't created as they're needed. At game load I create each Map (~200 or so) and each one has their own ContentManager. When you enter that map it loads all of the map's content, which increases the scratch buffer size to the largest asset it loads. When you leave the map it Unloads(), but it doesn't get rid of the ContentManager. Unless I dispose of the ContentManager myself or recreate it, it won't be GC'd. A better solution than the one I mentioned in my last post is to simply share a single ContentManager between all Maps since only one is ever loaded at once anyway. |
In that case, disposing the ContentManager when you unload the map and then recreate it when you need it will work. What I said above was in case you were reusing the same ContentManager on each map. EDIT |
I think using a pool of scratchbuffers is a good idea here. Most people will only use 1 at a time (like @gap9269), in which case only 1 buffer will be allocated. |
@Jjagg Can we use your ByteBufferPool for this purpose? Originally I had a memory pool too, specially for the ContentManager. 032bd7e#diff-2cc0151b6c7e917bf7f4accecbd54741 |
We can use ByteBufferPool. Just need to add a minimum array size. I think it's fine other than that. |
I'm curious about how many people will even run into this issue - as you mentioned above @Jjagg, no one else seems to have had a problem. At least not openly. I just switched to using a single ContentManager for maps and the problem is gone. Man, this one had been eating at me for months. Those garbage collections are me leaving an area, which would have caused an increase every time before this fix. Now it just sits flat until I load a larger asset, which I'll have to keep in mind going forward. |
What do you guys want to do with this issue? It isn't a Content.Unload() issue as the title implies, but it seems like there might be something that can be done to prevent people like me misusing ContentManagers and causing problems that are difficult to trace. On the other hand, it's entirely preventable on the user's side so I'm not sure it warrants a lot of effort being put into making a fix. |
After upgrading from 3.4 to 3.5.1 (and then 3.6) I noticed that calling ContentManager.Unload() doesn't lessen the amount of memory being used by the game, as seen in the Diagnostic Tools of VS2015 and the Resource Monitor on Windows. Loading, unloading, then reloading the same content would originally keep the memory usage about the same, but now it continues to increase during every Load() while not decreasing during Unload().
I tested this further by installing v3.4 again and using the same project and code. Loading and unloading the same content as before now has the desired effect where the memory usage increases during Load() and decreases after Unload(), as expected.
The text was updated successfully, but these errors were encountered: