Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
Featured Blog | This community-written post highlights the best of what the game industry has to offer. Read more like it on the Game Developer Blogs or learn how to Submit Your Own Blog Post
Avoiding performance issues in game development by managing garbage collection in Unity - some tips and tricks.
Originally posted to the RetroEpic blog. Written by Brendon Smuts - edited and reposted by Megan Hughes with permission.
Unity and the .NET/Mono platforms provide numerous tools to help us mitigate many of the headaches that we have come to associate with traditional low level game development. With little to no effort we have access to a robust content pipeline, platform abstraction, comprehensive standard libraries, a powerful runtime engine and much more.
With all the support systems provided to developers nowadays it is often mistakenly expected we no longer need to concern ourselves with the details they take care of. The truth is that naive use of these black boxes generally leads to its own set of non-trivial problems that tend to later manifest as poor performance. Due to the usually subtle nature of these missteps these issues can be very time consuming to locate and even more so to correct.
One of the core services of the Common Language Runtime (CLR) is automatic memory management and comes with a Garbage Collector (GC) which is used for cleaning up memory that is no longer be in use. Collection is performed by pausing your application, traversing the the hierarchy of references to objects and marking them as reachable. Unreachable objects are seen as free memory and remaining instances are moved around in memory to remove open spaces in a process called compaction.
For general applications this series of steps may be benign. In games however, where smooth performance is critical, these collections can result in a noticeable jitter in frame rate and should be more consciously regarded when writing code. The reality of garbage collection issues is that they are typically the result of hundreds or thousands of small errors that cumulate into a big problem. It is for this reason that the points below should not be regarded as premature optimisations but instead be treated as simply good practice. Once you start applying these principles to your own code they will quickly become second nature and you'll find yourself using them without needing think about it.
You're not going to get very far into making a game before you start finding yourself working work collections. Even though their individual contributions to the issue of garbage is generally minor, their prolific use makes them one of the primary culprits for GC issues. Even the benign looking code below can generate noticeable garbage.
public class Friend : MonoBehaviour
{
public Friend[] friends;
private void Update()
{
var closeFriends = new List<Friend>();
foreach (Friend friend in friends)
{
if (Vector3.Distance(friend.Position, Position) < 1f)
{
closeFriends.Add(friend);
}
}
ContactFriends(closeFriends);
}
...
Under the hood many of the standard .NET/Mono collections use regular raw arrays to store their references. Collections that can grow their capacity dynamically, a List for example, do so by newly constructing larger arrays and discarding their old ones which then become garbage. If you can predict the upper bound capacity that your collection will use, or some value close to it, it can be useful to create your collection with a preallocated capacity.
var closeFriends = new List<Friend>(friends.Length);
There are a number of ways to reuse an object you might otherwise regularly recreate. The easiest is to simply hold on to objects you know are going to be used later on. Instead of creating a new object every time you want to do some bit of work create it once and store it somewhere you can retrieve it later.
public class Friend : MonoBehaviour
{
public Friend[] friends;
private List<Friend> closeFriends;
private void Awake()
{
//Create our collection once with an upper bound capacity.
closeFriends = new List<Friend>(friends.Length);
}
private void Update()
{
foreach (Friend friend in friends)
{
if (Vector3.Distance(friend.Position, Position) < 1f)
{
closeFriends.Add(friend);
}
}
ContactFriends(closeFriends);
//Empty our collection for reuse next frame.
closeFriends.Clear();
}
...
.NET/Mono gives us a lot of cool language features intended to simplify the code we write. Often these features come at some small cost which, while negligible in general applications development, add up in real time games. Objects that implement the IEnumerable interface can be iterated over using a convenient foreach. Often, though not always, these implementations instantiate internal objects to track enumeration progress and are then discarded after use. In regularly called functions, or nested iterations, these can lead to a lot of garbage objects.
for (int i = 0; i < friends.Length; i++)
{
Friend friend = friends[i];
if (Vector3.Distance(friend.Position, Position) < 1f)
{
closeFriends.Add(friend);
}
}
Sometimes populating a collection of data you want to work on is the responsibility of some other object. Most of the time you'll find a method providing you an array or list as a return value for you to then work with. These collections, that are constructed on demand and passed back to the caller, are then thrown onto the garbage heap once work with them has completed. Some of the more commonly used Unity functions now let you provide your own pre-constructed collections that are then filled out for you. It can be beneficial when creating your own methods that return collections to follow a similar pattern.
public class Party : MonoBehaviour
{
public Friend[] friends;
private List<Snack> snacks;
private void Awake()
{
//We don't know how many snacks each friend will
//bring but lets assume at most 4
snacks = new List<Snack>(4);
}
private void Update()
{
for (int i = 0; i < friends.Length; i++)
{
Friend friend = friends[i];
friend.GetSnacks(snacks);
Debug.Log(friend + " brought " + snacks.Length + " snacks.");
snacks.Clear();
}
...
public class Friend : MonoBehaviour
{
private Snack[] snacks;
public void GetSnacks(List<Snack> outSnacks)
{
outSnacks.AddRange(snacks);
}
...
While this is by no means a completed and exhaustive list of methods of dealing with garbage collection in game development, I hope you've found some of these tips useful. Feel free to add any other tips or tricks that you have found helped you in your development process in the comments below or send us an email with your thoughts. We do actually love getting mail!
Read more about:
Featured BlogsYou May Also Like