Feature Preview: Incremental Garbage Collection
We have just added an experimental new feature to Unity 19.1a10: Incremental Garbage Collection. Read this post to learn what this feature is all about, how it can help your project, what all our future plans for it and how can you get involved in the process.
Why incremental GC?
The C# language uses managed memory with automated garbage collection, meaning that it uses an automatic method of tracking objects in memory and releasing the memory of any object which is no longer needed (See our documentation for more info). The benefit of this is that you generally don’t need to manually keep track of releasing any memory which you don’t need anymore because the garbage collector will automatically do that for you, which makes your work easier and also removes a big source of potential bugs. The downside is that the garbage collector takes some time to do its work, and this may happen in moments when you would rather not want to spend time on this.
Unity uses the Boehm–Demers–Weiser garbage collector which is a stop-the-world garbage collector, meaning that whenever it needs to perform garbage collection, it will stop the running program, and only resume normal execution once it has finished all its work. This can cause delays in the execution of the program at somewhat arbitrary moments, which can take anywhere between less than 1 and several 100 milliseconds, depending on how much memory the garbage collector needs to process and on the platform the program is running on. Now, obviously, for real-time applications like games, this can become quite a big issue, as it isn’t possible to sustain a consistent frame rate as required for smooth animation if the program’s execution can be arbitrarily suspended by the garbage collector. These interruptions are also known as GC spikes, as they will show as spikes in an otherwise smooth profiler frame time graph. Usually, developers try to work around this issue by writing their code to avoid creating “garbage” memory while running the game, so the garbage collector has less work to do — but this isn’t always possible or easy.
Enter Incremental Garbage Collection. With Incremental GC, we still use the same Boehm–Demers–Weiser GC, but we run it in an incremental mode, which allows it to split its work into multiple slices. So instead of having a single long interruption of your program’s execution to allow the GC to do its work, you can have multiple, much shorter interruptions. While this will not make the GC faster overall, it can significantly reduce the problem of GC spikes breaking the smoothness of the animation by distributing the workload over multiple frames.
To understand the impact of this, check these screenshots from the Unity Profiler of a little GC performance test script, running as a macOS Standalone build, without and with incremental GC enabled. The script is running at 60 fps. The light blue parts of the frame are “script operations” (simulated by a System.Threading.Thread.Sleep call in the script), the yellow parts are Vsync (ie, waiting for the next frame to begin), and the dark green parts are garbage collection.
Without incremental GC (above), the project shows spikes of around 30ms every few seconds, interrupting the otherwise smooth 60fps frame rate.
With incremental GC (above), the same project keeps its consistent 60fps frame rate, as the GC operation is broken up over several frames, using only a small time slice of each frame.
This screenshot shows the same project, also running with incremental GC enabled, but this time with fewer “scripting operations” per frame. Again, the GC operation is broken up over several frames. The difference is that this time, the GC uses more time each frame, and requires fewer total frames to finish. This is because we adjust the time allotted to the GC based on the remaining available frame time if Vsync or Application.targetFrameRate is being used. This way, we can run the GC in time which would otherwise be spent waiting, and thus get GC “for free”.
How to enable incremental GC?
Incremental GC is currently supported in Unity 2019.1 alpha on Mac, Windows and Linux Standalone Players and on iOS, Android and Windows UWP players. More supported platforms will be added in the future. Incremental GC requires the new .NET 4.x Equivalent scripting runtime version.
On supported configurations, Incremental GC is now available as an experimental option in the “Other settings” area of the Player settings window. Just enable the Use incremental GC (Experimental) checkbox, and build a player to give it a try.
You can get more precise control over incremental GC behavior using the new Scripting.GarbageCollector APIs added in 2019.1.
Once you’ve tested it on your project, please let us know how it went on the forum — we’d love to hear your feedback!
If you enable incremental GC, the garbage collector will split up the garbage collection work across multiple operations, which can then be distributed across multiple frames. We hope that in most cases where GC spikes were an issue, this will mitigate the symptoms. But Unity content is extremely diverse and can behave in very different ways — and it’s likely that there are cases where incremental GC may not be beneficial.
Specifically, when incremental GC breaks up its work, the part which it breaks up is the marking phase, in which it scans all managed objects to find which other objects they reference, to track which objects are still in use. This assumes that most of the references between objects don’t change between slices of work. When they do change, the objects which have been changed need to be scanned again in the next iteration. This can cause a situation where incremental collection never finishes because it will always add more work to do — in this case, the GC will fall back to doing a full, non-incremental collection. It’s easy to create artificial test cases changing all the references all the time, where incremental GC will perform worse than non-incremental GC.
Also, when using incremental GC, Unity needs to generate additional code (known as write barriers) to inform the GC whenever a reference has changed (so the GC will know if it needs to rescan an object). This adds some overhead when changing references which can have a measurable performance impact in some managed code.
Still, we believe that most typical Unity projects (if there is such a thing as “typical” Unity projects) can benefit from incremental GC, especially if they were suffering from GC spikes.
Incremental GC is included in Unity 2019.1 as an experimental preview feature. This has been done for a number of reasons:
- It isn’t yet supported on all platforms.
- As outlined in the “Expected Results” section above, we expect incremental GC to be beneficial or at least not detrimental performance-wise for most Unity content, and this seems to have been true for various projects we have been testing with. But as Unity content is very diverse, we want to make sure that this assumption stays true across the greater Unity ecosystem, and we need your feedback on this.
- The requirement for Unity code and scripting VM (mono, il2cpp) to add write barriers to inform the GC whenever references in managed memory have changed introduces a potential source of bugs where we have missed adding such a write barrier, which could lead to objects being Garbage Collected when they are still needed. Now, we have done extensive testing (both manual and automated) and we aren’t aware of any such issues, and we believe that this feature is stable (otherwise, we would not ship it). But, once again, because of the diversity of Unity content and because such bugs might turn out to be hard to trigger in practice, we cannot completely rule out the possibility that there may be issues.
So, overall we believe that this feature works as expected and there are no known issues with it. But, because of the complexity of the Unity ecosystem, we need some time and exposure to get the confidence to drop the experimental label, which we will do based on the feedback we get.
Incremental GC is available in Unity 2019.1 alpha now, but we expect a few changes to the feature in the course of the next year.
- Add support for all other player platforms
- Remove experimental label (this depends on your feedback)
- Add support for running the Editor itself in incremental GC mode.
- Make incremental GC the default option (this depends on your feedback)
Why did you not use [insert name] GC instead? I heard it’s really good!
When discussing incremental GC, we’re often asked why we haven’t considered using some other GC solution instead of Boehm GC, for instance Xamarin’s Sgen GC. We have considered other options (including writing our own GC) and will continue to do so, but the main reason for staying with Boehm and switching that to incremental mode instead is that this seems to be the safest step we can do while still getting significant improvements. As explained in the “Experimental Status” section above, one of the risks is introducing bugs due to missing or incorrectly placed write barriers. But the requirement of adding write barriers is shared by pretty much any GC solution more modern than what Unity uses today. So by staying with Boehm GC, we can somewhat isolate the risk created by having to add write barriers correctly from the risk of also switching to a completely different GC.
We will continue to watch developments in this area, and see how your needs develop with the introduction of incremental Boehm. If we will find that incremental Boehm still leaves a lot of our users struggling to deal with GC spikes or other issues, we will consider other options.