Introduction to garbage
'Garbage' is a memory allocation generated by some managed languages such as C# so that they can keep track of object references and save you from having to manage pointers manually. The Garbage Collector (GC) will then do a cleanup every X seconds to de-allocate all the references that aren't needed anymore. While this improves the ease-of-use of the language, it comes with an important performance cost. Every garbage collection event costs important CPU time, and can cause very noticeable framerate lag spikes. The good news is that this performance cost can be largely avoided if you understand how to code without generating garbage at runtime.
Note that you can trigger a garbage collection event manually at strategic times with "GC.Collect"
How to detect garbage
In Unity's profiler, there is a "GC Alloc" column. If you click on it and order things by the amount of garbage they generate, you can easily see all the things that generate garbage in your game. Notice here that Camera.Render generates 65 bytes and RenderPipeline 17 bytes per frame. These are internal Unity calls and are unavoidable, but anything that generates garbage other than that should be changed to not generate any constant garbage. It's always possible! (almost)
It's also important to note that some things generate garbage in-editor, but not in builds.
How to avoid garbage
'new MyClass()' and 'Instantiate()' create garbage
Creating a new class, Instantiating an object, etc... creates garbage. Use object pooling to only create objects at strategic times, such as at game initialization or during loading screens.
Creating a new struct does not generate garbage, so always consider using structs instead of classes when possible. However, you must be aware that structs are passed by copy and not be reference.
Also note that arrays and Lists are reference types, so creating them also creates garbage. Creating temporary arrays or Lists in an Update() loop is a very bad idea. Cache those arrays instead.
List operations create garbage
Adding objects to a list creates garbage. So if you need to do this kind of operation frequently, use a pre-initialized array instead of a list. For example, if you want to keep track of a list of transforms in proximity of a certain transform, initialize an array of transform with a pre-determined maximum size, and create a counter that keeps track of how many non-null elements are in the array:
public Transform[] proximityTransforms = new Transform[256];
public int proximityTransformCount = 0;
Just make sure your array is compact at all times (meaning there are no null values in-between valid values) and your counter is properly updated whenever you add/remove elements to your array, and then you can iterate on your array like this:
for(int i = 0; i < proximityTransformCount; i++)
{
// proximityTransforms[i] .......
}
A Compact Array example is available here: Compact Array
Strings create garbage
Strings create garbage. That means that the following code....
void Update()
{
if(this.name == "test")
{
// ...
}
}
... creates garbage constantly, because declaring "test" creates a new string object. A way around this is to cache your strings beforehand:
private string testString = "test";
void Update()
{
if(this.name == testString)
{
// ...
}
}
Use 'NonAlloc' Unity methods
Several methods, such as SphereCastAll() and OverlapSphere(), create garbage because they must return a new array of hits or colliders. Always use SphereCastNonAlloc() and OverlapSphereNonAlloc() instead.
Delegates and UnityEvents creates garbage
- Adding listeners/methods to C# delegates, Actions, and UnityEvents creates garbage
- Invoking UnityEvents creates garbage every time, but invoking c# delegates and actions does not
Coroutines create garbage garbage
Starting a new coroutine creates garbage, and yielding a "new" object creates garbage (like "yield return new WaitforSeconds"). Cache your coroutines and your WaitForSeconds to avoid creating garbage.