How to Disable the GC
Unity’s GC is a continual thorn in our sides. We’re constantly working around it by pooling objects, limiting use of language features, and avoiding APIs. We even call GC.Collect
on load screens in the hopes that the GC won’t run during gameplay. Today’s article goes one step further and shows how to disable the GC completely so there’s zero chance it’ll run. We’ll also see how to turn it back on when we’re ready for it again.
Unity’s GC is implemented in C code that ships with Unity. Here’s where it is in a Mac installation as of 2017.3:
/Applications/Unity/Unity.app/Contents/il2cpp/external/boehmgc/
In the misc.c
file, we see these functions about 1800 lines down:
GC_API void GC_CALL GC_enable(void) { DCL_LOCK_STATE; LOCK(); GC_ASSERT(GC_dont_gc != 0); /* ensure no counter underflow */ GC_dont_gc--; UNLOCK(); } GC_API void GC_CALL GC_disable(void) { DCL_LOCK_STATE; LOCK(); GC_dont_gc++; UNLOCK(); }
These are very straightforward functions. They just increment an integer (GC_dont_gc
) that serves as a global flag for whether the GC is enabled or not. If it’s zero, the GC is enabled. If it’s non-zero, the GC is disabled. So we should be able to call GC_disable
to disable the GC and then GC_enable
to re-enable it.
So let’s try calling these functions from C# using the [DllImport]
attribute:
using System.Runtime.InteropServices; public static class GcControl { // Unity engine function to disable the GC [DllImport("__Internal")] public static extern void GC_disable(); // Unity engine function to enable the GC [DllImport("__Internal")] public static extern void GC_enable(); }
Now we can call GcControl.GC_disable()
or GcControl.GC_enable()
from anywhere in the app to disable and re-enable the GC. It’s important to remember that these functions increment and decrement an integer in a manner similar to reference counting. Calls to GC_disable
should therefore be matched with calls to GC_enable
.
Now let’s try it out with a little app that displays the number of times the GC has run and a button to toggle the GC on and off. Every frame, we’ll allocate 100 KB of managed memory in a byte[]
and hold onto it so the memory pressure just keeps increasing and causing the GC to run.
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.UI; class GCTestScript : MonoBehaviour { // UI public Text gcCountText; public Text toggleButtonText; // GC status private bool isGcDisabled; // Holds allocated memory // Prevents GC from collecting it private List<byte[]> storage; void Awake() { storage = new List<byte[]>(); toggleButtonText.text = "Disable GC"; } void Update() { // Display how many times GC has run int gcCount = GC.CollectionCount(0); gcCountText.text = gcCount.ToString(); // Add managed memory pressure storage.Add(new byte[100*1024]); } // Toggle GC on or off public void OnToggle() { isGcDisabled = !isGcDisabled; if (isGcDisabled) { GcControl.GC_disable(); toggleButtonText.text = "Enable GC"; } else { GcControl.GC_enable(); toggleButtonText.text = "Disable GC"; } } // Re-enable GC on exit void OnApplicationQuit() { if (isGcDisabled) { GcControl.GC_enable(); } } }
Here’s how it looks in action:
Clicking the button to disable the GC does stop the GC collection count from rising. Clicking the button to re-enable the GC resumes the number’s rise as the GC kicks in to try to reclaim memory to make room for newly-allocated byte[]
objects.
This works in both the Unity editor and on an Android device. There’s no guarantee that it’ll work on every single platform that Unity supports, but at least these two work.
So now that we have this tool at our disposal, how should we use it? To decide on a strategy, it’s important to understand the effects of the tool. Running the game with the GC disabled is new territory for most of us.
Normally, with the GC enabled, when we allocate managed memory such as by using new Class()
and there isn’t enough room in the managed heap to store that object, two things happen. First, the GC is run to collect any garbage so the memory can be reused. Second, if there’s still not enough room, the managed heap is expanded to make room for the new object.
With the GC disabled, the first step won’t occur and there will be no possibility of reusing the memory of one or more garbage objects. Instead, we’ll proceed directly to step two and expand the managed heap. This means that memory usage will continue to grow as we allocate more and more objects. If we allocate enough, we may run out of memory and face consequences such as degraded performance due to using “virtual memory” (e.g. on Mac) or having our app terminated outright by the OS (e.g. on Android). Disabling the GC simply does not allow us to abandon all self-control and allocate as much as we want.
Since we really don’t want these things to happen, it’s vitally important that we don’t use too much memory while the GC is disabled. It’s often possible to arrange for this. For example, if we start out at 100 MB of memory usage after loading a level of the game and we know that we won’t allocate more than 100 MB of memory during the level, then we’re fine on any device where we can use up to 200 MB. As a safeguard, we can always query the memory usage periodically and re-enable the GC if we reach a threshold (e.g. 250 MB) that’s considered dangerous for the device.
It’s also worth noting that these C functions we’re calling are internal to the Unity engine, not part of the public API. There’s no guarantee that the function names, signatures, or behaviors will remain the same from version to version. Practically though, these functions are part of an off-the-shelf GC that Unity uses and the latest date in the file header is 2001, so it’s likely that this technique will keep working until Unity eventually replaces the GC. That’s currently on the roadmap under “Development” with the description “In-progress, timelines long or uncertain.”
Still, this is definitely an advanced technique and it should only be used with great caution. Disabling the GC for framerate-critical sections of the game and re-enabling it afterward (e.g. on a loading screen) may be a great benefit that’s worth it for some games, but it should be used very carefully.
#1 by Stas on May 16th, 2018 ·
This is amazing news to us! Weird that I couldn’t find any option to disable GC in Unity before your post, though as you say it was available for a while even if this is not a part of public API.
#2 by Roman on May 18th, 2018 ·
EntryPointNotFoundException: GC_disable
Tried to launch it in 2018.1 :(
Just copy-pasted your code, looks like something has changed?
#3 by jackson on May 18th, 2018 ·
I just tried it out with 2018.1 in macOS on both editor and standalone builds using IL2CPP and it worked just fine. What OS and build settings didn’t work for you?
#4 by Roman on May 19th, 2018 ·
Windows 7, Settings : http://i.prntscr.com/LUgPBw6kSOuB6mRfYHYnPg.png
I switched to IL2CPP and it works in standalone build now
But it still doesn’t work in editor :(
I modified script a little bit : http://i.prntscr.com/FqR6qh7fR2_kmA18Rgo9qQ.png
#5 by jackson on May 19th, 2018 ·
I’m glad to hear it’s working in the standalone build. You might want to add
#if !UNITY_EDITOR_WIN
in yourEnable
andDisable
to avoid this error in Windows Editor, where it’s apparently not supported.#6 by Ben Hymers on June 29th, 2018 ·
I’m also finding this no longer works with Unity 2018.1.0f2, .NET 4.6 runtime, Mono backend (not IL2CPP), Windows 64 bit Standalone, on Windows 10. Any idea why? I don’t even know where to look to search for it really!
#7 by jackson on June 29th, 2018 ·
I tried with Unity 2018.1.1f1 on 64-bit Windows 10 and got an error both in editor and in a Mono standalone build:
Then I tried an IL2CPP build and it worked just fine. This also worked on macOS with Unity 2018.1.0f2. So it seems that this just doesn’t work with Mono on Windows in 2018.1, but can be worked around where it counts most (standalone builds) by opting to use IL2CPP.
#8 by PZB on December 28th, 2018 ·
Great! and I found it’s only work on IL2CPP build not Mono.
I’m also very curious about how can you find out that way ? because I have searching a way to disable GC half year ago and got nothing!
#9 by jackson on December 29th, 2018 ·
There is experimental support in Unity 2018.3 for disabling the GC, so you might want to try that out.
#10 by MG on May 21st, 2019 ·
Thanks for the info!
It seems that since 2018.2, the functions are called `il2cpp_gc_enable` and `il2cpp_gc_disable`, defined in `Editor\Data\il2cpp\libil2cpp\il2cpp-api-functions.h`.