Unity Memory Management: How to manage memory and reduce the time it takes to enter play mode

Lynxelia Nyx
9 min readFeb 10, 2018

--

A lot of people have been running into the issue where the editor will take a really long time to load the scene after clicking “Play”. While many others have asked how to fix this, few answers have been offered, and a lot of the proposed solutions do not help.

“It won’t start for 30–40 seconds after I hit play. Anyone else seeing this?”

“Is there something I can do to reduce time that it takes unity to enter Play Mode?”

“There is a problem when I enter the play mode. The first time I launch Unity everyday it will cost about 20 minutes. If I don’t close the editor, every time I enter play mode will cost about 30 seconds later. It’s unbearable.”

“Time spent waiting on entering playmode is a waste and can seriously hinder iteration times. In an empty project it already is ~1.5s for no appearant reason. In larger projects (Ori and the blind Forest) it got upwards of 20 seconds, up to a whole minute in extreme cases… All in all, this is wasting a lot of time for every developer”

After our “EnterPlayMode” time started approaching a full 30 seconds, our team decided to do a deep dive on this issue and investigate what causes this slow down.

Here is a full breakdown of the various unity quirks and bugs that cause the wait time to get unnecessarily long and solutions to fix or workaround these issues.

Step 1. Profiling

If you are experiencing really long enter play mode times, the first step to debugging it is to use the profiler.

Use Window->Profiler to open the profiler. If it’s not already there, use the “Add Profiler” button to add a profiler for CPU. Make sure “Profile Editor” is on and leave “Deep Profile” off (In our experience, deep profile wasn’t helpful when we were debugging this issue anyway). Toggle on “Record”. Now open a scene in your project and hit “Play”.

After your scene loads you should see a big spike in the CPU graph in the Profiler. Quickly toggle off “Record” to stop recording, then click this spike in the graph. The first item you should see in the Overview is “EnterPlayMode”. If you don’t see it, use the forward (>) and back (<) buttons to move forward and back a few frames around the spike until you see it. Once you find it, unfold “EnterPlayMode”. If you are experiencing the same sort of slow down we did, the first thing under “EnterPlayMode” will probably be “ReloadAssemblies” and under that “SerializeBackups” and “DeserializeBackups”. If these are not the first things under EnterPlayMode, skip to step 5 and try the solutions we suggest there.

Step 2: Force garbage collection when opening scenes

The main reason why unity spends so long in SerializeBackups and DeserializeBackups is because it is serializing all the objects currently present in memory. If you add a “Memory” profiler to your profile window and click the “Take Sample: Editor” button, you will probably see a lot of objects under Assets.

You may be wondering, why are all these objects in memory if they are not in my current scene? One thing to keep in mind is that even if you have a project that only has one scene and one object in that scene, if that object has a field reference to other objects (e.g. public List<GameObject> m_CharacterObjects), unity will load all those referenced objects into memory (e.g. it will load each character game object into memory). And if those objects have field references to other objects (e.g. each character has a reference to a list of armor game objects), unity will also load all those referenced objects into memory (e.g. it will load each armor game object into memory), and so on and so on down the chain. This can result in a whole lot of objects being loaded into memory that you didn’t expect. And these objects will always be loaded, even if you never end up using them.

While this may not be a problem in of itself, a big issue with the unity editor is that it does not clean up memory allocated for objects when you open a new scene. For example, if you open a scene in your project that has a lot of objects in it, and then open another scene, the objects from the previous scene will still be hanging around in memory. Even if you create a brand new empty scene, all those old unused objects will still be in memory. They will never go away until you close unity. And if you keep opening one scene, then another, the memory usage will just keep going up and up with every scene you open.
This is one of the main reasons why the time it takes to enter play mode gets so long, especially in projects with a lot of scenes, objects, or field references to other objects.

To fix the problem of objects lingering in memory from previous scenes, you’ll need to force unity to unload all these unused asset references and then garbage collect them all. Here’s a script that we use that will do just that every time you open a scene in unity:

You can also use this script to force garbage collection at any time using Tools/Force Garbage Collection

Step 3: Release managed references OnDestroy

Even if you include the “EditorSceneMemoryManager” script above, you may notice that some asset references are still sticking around even after forcing garbage collection. The most common reason for this is “ghost references”. Ghost references are references that exist in destroyed objects. They prevent the garbage collector from recognizing that these assets are no longer being used.

You might expect that when an object is destroyed, it releases its references to other objects so those objects can be garbage collected: For example, you might think that if a MonoBehaviour has a Sprite field in it and that Sprite field is assigned a value in the inspector, when that MonoBehaviour is destroyed, that assigned Sprite will be considered no longer referenced and will be garbage collected. This is NOT the case. Even after destroying the MonoBehaviour, a ghost reference will still point to that Sprite and the Sprite will never be garbage collected. It’ll just persist and continue to take up memory until you close unity.

So if you write your MonoBehaviour like this, any sprite you assign from the inspector will hang around in memory forever and never be garbage collected:

The solution is to set the Sprite field to null in the MonoBehaviour’s OnDestroy method.

Luckily, all MonoBehaviours in our project inherit from one base MonoBehaviour class. So instead of making this change to all our classes, we can simply use reflection to automatically null out any nullable field OnDestroy in that base class. (Also in addition to nulling out fields, it’s a good idea to also clear lists and dictionaries):

If you don’t already have your own base monobehaviour class that you can add this code to, we highly suggest creating one and then replacing all references in your project to “ : MonoBehaviour” with “ : BaseMonoBehaviour”

NOTE: We pointed out this issue to unity. They have replicated it and said that they will fix the issue in a later version of unity. Until then, the only way to fix the issue is the workaround posted above.

Step 4: Don’t set fields on other components during OnValidate (Or just replace OnValidate entirely)

This one is a weird unity bug. We’re not really sure why it happens, but we do have a workaround. Basically, OnValidate seems to mess with garbage collection if you use it to set a field on another component.

For example:

Attaching a MonoBehaviour like this to a GameObject will cause that GameObject and all its managed references to never be garbage collected no matter what. This issue caused assets to stick around for us even after following all the steps above.

Luckily we have a workaround for this. We mainly use OnValidate to validate properties whenever a dev changes those properties in the inspector (e.g. we have an int field that we don’t want to allow to go lower than 0, so if it’s negative, we set it to 0 during OnValidate). We don’t actually want this validation to occur any other time (e.g. when the asset is loaded or when entering play mode). So as a work around, we rolled our own “OnValidateProperty” method. This method is guaranteed to only execute when a property is changed in the inspector, nowhere else. Moving all our code from OnValidate into this method prevents all those weird issues that trip up the garbage collector.

To use this workaround, instead of inheriting from MonoBehaviour, you need to make sure all your MonoBehaviours inherit from a base MonoBehaviour class with the following OnValidateProperty method (though for cleanliness I am omitting it below, remember to also include the OnDestroy code from Step 3 in this BaseMonoBehaviour class):

And then instead of inheriting from Editor, make sure all your MonoBehaviour editors inherit from a BaseMonoBehaviourEditor class like this:

This way in your class you can easily validate properties without worrying about it tripping up the garbage collector and taking up scene load time.

Also if you have custom editors, keep in mind you can always override the DrawProperty method if you want to change what a control looks like in the inspector.

If you follow this step and all the others outlined above, you should no longer have any lingering references when switching scenes. Woo!

Step 5: EditorApplication.isPlayingOrWillChangePlaymode

If you got this far and your scene load times still aren’t as low as you would like, a good strategy is to check the profiler for any editor-only code that takes a long amount of time, and surround it with if(!EditorApplication.isPlayingOrWillChangePlaymode)

The two most common sources of time intensive editor-only code (other than custom inspectors or custom windows) are InitializeOnLoad and OnValidate

  1. InitializeOnLoad: Search your project for any classes marked with the attribute [InitializeOnLoad]. Any classes marked with this attribute will be run when unity starts up AND every time you enter play mode. The unity documentation mentions the former but does not mention the latter. If you do have classes marked with [InitializeOnLoad], make sure you surround anything in their constructor with the “entering play mode check” above. Keep in mind, even if you don’t remember adding this attribute anywhere in your code, this is a common attribute that is used in lot of asset store packages and unfortunately the creators of these packages don’t realize that this code will be executed every time you enter play mode. In our case, we included a script in our project that automatically scans for empty folders in the project and deletes them every time unity is opened. (We included this script so we didn’t have the meta hell caused by git and unity whenever someone moves files around and deletes/renames folders.) What we didn’t realize was that this empty folder check wasn’t only happening on starting up unity but also every time we entered play mode, which increased the play mode enter time significantly.
  2. OnValidate: OnValidate will be called on every object in your scene while entering play mode, so make sure you don’t have anything too time intensive in your OnValidate methods. Calls to GetComponent, GetComponentsInChildren, GameObject.Find, FindObjectOfType, etc, in OnValidate will greatly increase the time it takes to enter playmode. If you do have time intensive code in your OnValidate methods, surround the code with the “entering play mode check” above. Keep in mind if you followed step 4 and replaced OnValidate with OnValidateProperty, you don’t need to perform this check since OnValidateProperty will only ever be executed when a dev changes a property through the inspector. It won’t execute when entering play mode like OnValidate does.

All right, you’ve made it to the end! If you’ve followed all the steps above, you should be experiencing much faster scene load times. Doing the above things allowed us to get our scene load time down from 30 seconds to a brisk 5 seconds. Hopefully you are seeing similar results as well!

You can view the full log of our investigation in this thread: https://forum.unity.com/threads/editor-is-taking-20-seconds-to-enter-playmode-in-an-empty-scene.516004/

If you have any other questions on topics we covered or need further help debugging “EnterPlayMode” load times, leave a comment below. Good luck!

--

--