One typical piece of advice for dealing with the slowness of Unity’s garbage collector is to periodically force a garbage collection, such as every 30 frames. Even Unity advises this. The idea is that you’ll spread out the garbage collection work across many frames rather than having a big spike that causes your frame rate to stutter. But the question remains- what’s the best rate to force the GC? Today’s article tries out various intervals to see which is best. Read on for the results!

The basic idea here is very simple. Just call GC.Collect() every N frames from one of your Update functions:

if (Time.frameCount % 30 == 0)
{
	System.GC.Collect();
}

But why collect every 30 frames? Why not 20 or 40 or 1 or 60? Which value of N frames is ideal?

To try that out, I made a little script that collects every 1, 10, 20, 30, 40, 50, 60, 70, 80, 90, and 100 frames. I measured the time it took to collect garbage until I got 5 milliseconds worth of collect calls. Meanwhile, I allocated a 1 KB byte array every frame to create some garbage.

Here’s the script:

using System;
 
using UnityEngine;
 
public class TestScript : MonoBehaviour
{
	private const int AllocSize = 1024;
	private const int GcAccumMillis = 5;
	private static readonly int[] frameCounts = { 1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
 
	private byte[][] buffers = new byte[100000][];
	private int bufferIndex;
	private long totalTicks;
	private long numCollections;
	private System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
	private string report = "Interval,Time\n";
	private int frameCount = frameCounts[0];
	private int frameCountIndex;
	private long gcAccumTicks;
 
	void Awake()
	{
		gcAccumTicks = (System.Diagnostics.Stopwatch.Frequency * GcAccumMillis) / 1000;
	}
 
	void Update()
	{
		if (frameCountIndex == frameCounts.Length)
		{
			return;
		}
 
		if ((Time.frameCount % frameCount) == 0)
		{
			for (var i = 0; i < bufferIndex; ++i)
			{
				buffers[i] = null;
			}
			sw.Reset();
			sw.Start();
			GC.Collect();
			totalTicks += sw.ElapsedTicks;
			numCollections++;
			if (totalTicks > gcAccumTicks)
			{
				var totalMillis = (totalTicks * 1000) / System.Diagnostics.Stopwatch.Frequency;
				report += string.Format(
					"{0},{1}\n",
					frameCount,
					totalMillis / (float)numCollections
				);
				totalTicks = 0;
				numCollections = 0;
				frameCountIndex++;
				if (frameCountIndex == frameCounts.Length)
				{
					return;
				}
				frameCount = frameCounts[frameCountIndex];
			}
		}
 
		buffers[bufferIndex] = new byte[AllocSize];
		bufferIndex++;
	}
 
	void OnGUI()
	{
		if (frameCountIndex == frameCounts.Length)
		{
			GUI.TextArea(new Rect(0, 0, Screen.width, Screen.height), report);
		}
	}
}

If you want to try out the test yourself, simply paste the above code into a TestScript.cs file in your Unity project’s Assets directory and attach it to the main camera game object in a new, empty project. Then build in non-development mode for 64-bit processors and run it windowed at 640×480 with fastest graphics. I ran it that way on this machine:

  • 2.3 Ghz Intel Core i7-3615QM
  • Mac OS X 10.11.3
  • Unity 5.3.2f1, Mac OS X Standalone, x86_64, non-development
  • 640×480, Fastest, Windowed

And here are the results I got:

Interval Time
1 0.4545455
10 0.4545455
20 0.4545455
30 0.4545455
40 0.5
50 0.4545455
60 0.5
70 0.5
80 0.4545455
90 0.5
100 0.4545455

Periodic GC Performance

As you can see, the times are pretty much constant regardless of the number of frames you choose to skip between garbage collection. On this machine, each GC.Collect() call took about 0.5 milliseconds. That’s about 3% of a frame at 60 FPS or 4.5% of a frame at 90 FPS (e.g. for VR).

These numbers, of course, should be taken with a grain of salt. A typical game won’t have exactly one 1024-element byte array of garbage per frame. It’ll probably have a bunch of little objects of various sizes. You’ll need to test with your particular game to find out if you should periodically run the garbage collector or not, and at what interval. To check, run Unity’s profiler and look for the frames where the garbage collector kicks in.

Do you run the GC periodically in your games? What interval has worked best for you? Share your thoughts in the comments!