What is the Garbage Collector? Let’s look at a definition on MSDN:
The .NET Framework’s garbage collector manages the allocation and release of memory for your application. Each time you create a new object, the common language runtime allocates memory for the object from the managed heap. As long as address space is available in the managed heap, the runtime continues to allocate space for new objects. However, memory is not infinite. Eventually the garbage collector must perform a collection in order to free some memory. […]
If C# or JavaScript is your first (and only) programming language then most probably you are not aware of how difficult is to manage application memory. In languages like C/C++ you have to decide when memory is allocated and when it is freed. Make one mistake, and you will either have a memory leak or your application will crash in an instant.
Of course if something is difficult to manage then it requires time to think about it. Now, we live in the world where we want to release our products to the market as soon as possible, so we’re trying to find a way to make everything as simple as possible. One of these things is allowing garbage collector to take care of freeing the memory.
Sounds great, doesn’t it? Yeah, garbage collection is a real life saver, but it has a downside too. Garbage collector runs from time to time and it requires your application to freeze for the time being (There’s a garbage collector mode that allows to run without freezing the app, but you’re not allowed to tune the Unity garbage collector settings.) and you have little control when the garbage collection should be launched. This is not an issue for a business application where the interface usually static, but may be an issue for a game where even a short pause can cause the player to loose the game. And what players hate the most is losing when it’s not their fault…
Garbage collector and games
Last time we talked a little about Unity profiler and garbage collection. The rule is simple – we don’t want the garbage collector to start its job. To do so, we have to stop generating garbage. Of course it’s impossible not to generate garbage at all, but we can significantly reduce amount of generated garbage, so garbage collector will run once per ten minutes instead of once per ten seconds.
Using Pools
One of techniques to minimize the garbage is to reuse existing objects instead of creating new. This technique is called object pooling and we talked about it last time. In most cases you will benefit the most by using this technique, so this should be the first thing you should take care of.
There’s a great free library that you can use to build your own pools. It’s called Smooth.Foundations and it is available on the Asset Store. Unfortunately the website with its documentation is down, but the code is really simple to understand. Here’s an example pool that creates a pool of GameObjects with SpriteRenderer component.
public class Pools { public static class SpriteRenderer { private static readonly Pool<SpriteRenderer> _instance = new Pool<SpriteRenderer>( () => { var go = new GameObject(); return go.AddComponent<SpriteRenderer>(); }, sr => { sr.sprite = null; sr.color = Color.white; }); public static Pool<SpriteRenderer> Instance { get { return _instance; } } } }
From line 6 to 10 we create a delegate that defines how pool object should be created. From line 11 to 15 there’s code responsible for resetting the object to its original state (after releasing it).
Then the pool can be used this way:
SpriteRenderer spriteRenderer = Pools.SpriteRenderer.Instance.Borrow(); // do something with spriteRenderer // and when it is no longer required: Pools.SpriteRenderer.Instance.Release(spriteRenderer);
Using Arrays
You may use arrays, but remember not to allocate these during the runtime. If any function needs to return an array of values, use an existing array and pass it as an argument.
Usual approach
void Start() { int[] numbers = GetNumbers(); for (int i = 0; i < numbers.Length; ++i) { Debug.Log("Got number: " + numbers[i]); } } int[] GetNumbers() { var arr = new int[3]; // will allocate an array each time called arr[0] = 1; arr[1] = 2; arr[2] = 3; return arr; }
GC-friendly approach
// allocated only once private static readonly int[] intArray = new int[32]; void Start() { int size = GetNumbers(ref intArray); for (int i = 0; i < size; ++i) { Debug.Log("Got number: " + intArray[i]); } } int GetNumbers(ref int[] arr) { arr[0] = 1; arr[1] = 2; arr[2] = 3; return 3; }
As you may have noticed, using gc-friendly code requires some more effort. First, you have to know the upper limit (in this case 32). You have to be sure that this limit is not exceeded, because your game will crash. Then if you want to iterate through these items, you have to use the size value instead of the size of an array.
Note that I used the ref keyword here. Ref means that this value should be passed by reference but since arrays are always passed by reference, it is not required in this case. The only reason it was used, was to increase the readability of GetNumbers() method call. If you see a ref then you know that something will get out that function using the passed parameter.
Using Lists
Using arrays directly may be not the best choice. Instead, you can use a List. The List in fact is an Array wrapped by a quite useful handling code, that guarantees you safety and you don’t need to worry about array sizes to much (and some other things too).
Yet you have to be careful. If you exceed the List array size, it will allocate a new bigger array. Since we want to minimize the number of allocations, always create lists using constructor that takes the capacity:
var list = new List<int>(32);
What about iterating through a list? You may know that or not, but Mono compiler that comes with Unity (Current Unity version at the time of writing this article is 5.2.3) has a bug that allocates memory when using foreach loop on collections. It’s not much because it is “only” 24 bytes, but executed many times will quickly exhaust your memory and trigger garbage collection. Instead, use for loop whenever possible.
Here’s an example code. Note that this time we don’t need a size variable.
// allocated only once private static readonly List<int> intList = new List<int>(32); void Start() { GetNumbers(ref intList); for (int i = 0; i < intList.Count; ++i) { Debug.Log("Got number: " + intList[i]); } } void GetNumbers(ref List<int> list) { list.Clear(); list.Add(1); list.Add(2); list.Add(3); }
Consolidating strings
There are some things that you simply cannot get around. One of those things is consolidating strings. When done, it will always allocate a new string. You can optimize this process a little by using StringBuilder instead of the add (+) operator.
The add (+) operator approach:
int points = 5; int total = 10; string str = "You have " + points + " out of " + total + "!"; Debug.Log(str);
The StringBuilder approach:
int points = 5; int total = 10; using (var disposable = StringBuilderPool.Instance.BorrowDisposable()) { StringBuilder sb = disposable.Value; sb.Append("You have "); sb.Append(points); sb.Append(" out of "); sb.Append(total); sb.Append("!"); Debug.Log(sb.ToString()); }
Have you noticed that I used a using instruction to get a Disposable object from the pool? This is another method to use the pools. It’s much safer, because you won’t forget to take the borrowed object back.
Is that everything?
Of course it isn’t! One note though. Please, keep in mind that running your code in Unity editor will generate more garbage than running it on the target device. You may find yourself in a situation when you try to optimize the garbage when it is not generated at all on the target device. This is because the Unity does some nice optimizations when your game is build, and you always should run the profiler on already built game to know what should be optimized and what not.