[Check the original post at Unity Save Memory Within OnDisable]
One of my students, Ryan, asked me last week how he could rescue the memory Unity was stealing from his game because of disabled game objects. Luckily, he was using Addressables, so I prepared an experiment...
In this post you'll learn:
- When is it ok to have deactivated GameObject's
- Why disabled GameObjects pose a threat to memory
- How to get your memory back without destroying them
With Unity, there are many reasons you might want to deactivate entire GameObject hierarchies in your scenes...
- Maybe you don't want specific elements to be visible or executing yet.
- Or maybe you're doing object pooling.
In any case, deactivating a game object effectively disables invoking the Update behavior.
And that's great, because deactivating a GameObject will save you precious CPU cycles.
However, it's not all perfect...
There's one thing we're not saving on when disabling objects: memory.
You see, keeping a direct reference to your assets will keep them loaded in memory. It doesn't really matter if they're invisible, disabled or whatever. Direct reference = memory greed.
But today I have a trick up my sleeve for you.
I'll show you how to recover the memory your disabled game objects are feeding on.
No worries, we'll make sure they get their memory back once they awake. We're not that selfish after all.
Quick Navigation (redirects to new tab)â€‹
A Typical Scenario: Wasting Memory
In this chapter, I'll show you a simple Unity scene that contains a few referenced assets.
You'll see how they refuse to exit memory when we deactivate their objects.
As simple as it is, this represents a vast amount of examples I've seen across a multitude of projects.
So my guess is that you suffer from this in your project.
To make a case for the post, I created a simple Unity scene with the following scene hierarchy:
- Pause Canvas
- Image0 → Sprite0
- Image1 → Sprite1
- Image2 → Sprite2
- SoldierCharacter → Static Mesh + 4 Materials
In this case, it doesn't always make sense to keep these objects active...
- If you're not paused, there's no need to keep their sprites in memory.
- If the soldier is pooled and waiting for its turn to spawn, it's better to keep it deactivated.
You might say: If I want the memory, I can just destroy it.
And you'd be right. You could destroy them to save memory.
However, some game architectures rely on persisting state within your objects. You might want to store the last parameters the pause menu had. Or you might want to save the customization parameters of that soldier that we will spawn after crossing that door.
No matter what, there'll be circumstances where you'll have deactivated objects for certain period. And it might be wise to save memory on those until the moment of the activation occurs.
Here's how the setup scene looks like (don't judge my art style, I'm a programmer)
Saving Memory OnDisable: Setup Scene
As you can see in the GIF below, deactivating the whole hierarchy won't release a single bit of memory (actually I'm not sad about it, as this keeps me employed).
Unity Default Behavior: Memory Greed
And that's the problem we are about to tackle in the next chapter.
An Alternative Scenario: Saving Memory
In the previous chapter, we've seen how deactivating game objects help reduce CPU usage. But it doesn't really help us reducing our memory usage.
In our example, we still pay memory for the following asset types:
In this chapter, we'll get rid of this memory cost in a sneaky way.
We're going to use Addressables.
And the trick is going to be a relatively simple script.
From a high-level perspective, this is what we're going to do:
- We're creating a simple script that holds an indirect reference to the asset our original component is using. This could be an indirect reference to a sprite being used in a UI Image, or a material in a MeshRenderer or a mesh in a MeshFilter.
- We then remove the direct reference from the original component (Image, MeshFilter, MeshRenderer).
- The script will load the asset during OnEnable and assign it to the correct field.
- When we disable the object in OnDisable, we unload the content.
This approach simply works for a simple reason...
When someone decides your content must be visible, your game object will be activated. This, in turn, will call our script's OnEnable function where we will load the relevant asset.
The same applies in the other direction. When we're done with an object, we either disable or destroy it. But whatever function gets called is not relevant for us, as Unity calls OnDisable for us and there we'll get rid of that memory section.
In my example, I implemented this simple mechanism for 3 components:
- MeshFilter: indirect reference to a Mesh
- MeshRenderer: indirect references to Materials (and therefore textures)
- UI Image: indirect reference to a Sprite
And you are just about to see the results of this script in the profiler...
Unity Addressables: OnDisable Memory Gain
Yeah, as I said, that just works.
And the good news is that you can extend this to other components and asset types such as SkinnedMeshRenderer and the like.
Unity: Save Memory - Conclusion & Comparison
So far, we've seen how Unity's default behavior doesn't encourage efficient memory usage patterns.
But we also saw a way to fight the battle and reclaim our memory.
A question that remains unanswered is... is this fight worth the price?
As you might have noticed, this solution isn't free of side-effects.
To use it, you must be willing to pay a price: latency.
Indeed, loading an asset asynchronously will often take several frames. Depending on the frame-rate you're targeting, it might be unnoticeable but there's always the chance you can spot the small delay.
However, often enough you'll just need the memory savings. This is especially true if you're targeting older Android devices.
And honestly, it's not always about needing. Every MB you save on memory will reduce the chances of crashes.
You never know what the user is running at any given time, so the less memory you use, the better off the reviews of your game will be.
To quickly recap what we achieved with this Addressables-based tool, here's what we got:
That's a respectable saving on this particular scene, I must say. Of course, this will immensely vary depending on your use case.
Try it out in your game and let me know your thoughts.
This tool works by:
- Having a basic Addressables setup (check my tutorial on Unity addressables)
- Attaching and setting up Mesh_ManageMemoryLifecycle to your static mesh or Image_ManageMemoryLifecycle to your UI image.
Remember: the sprites, meshes and materials you want to target must be addressables-activated.
Here's how the components look like:
OnDisable: Memory Gains for Meshes
OnDisable: Memory Gains for UI Images
Neat, isn't it?