Basics of Optimization in Unity
Objective: Optimization Areas
When it comes to performance optimization, we have a few key topics that can make a massive difference to how your games perform.
Caching:
Let’s use the example in the image above. Looks pretty simple. Using GetComponent to get the Mesh Renderer, get it’s material then its Color and set it as a new color with randomly generated values. A simple line of code that just changes the color of a gameobject.
The problem here is that every frame Unity has to get the renderer and then the material and then the color. At 60FPS that means Unity has to get that component 60 times in one second before ever doing anything else.
Now we take the code in the above image. Still not perfect but now Unity has the renderer cached which means it can skip looking for that 60 times a second but we can still make this better. Right now Unity has the renderer but has to get the material. Why not cache that since we really don’t need the renderer itself but the material.
For the example, I’ve made 20 evenly spaced Cubes all with the initial GetComponent in the Update method. Let’s check the Profiler.
The above is using GetComponent in the Update method. Notice the Update method is creating 1.8K of Garbage Collection. (Code in image Ex.1)
By just Caching the MeshRenderer, we have reduced the same frame from 1.8K to 1.1K (Code in image Ex.2)
Here is where we see the biggest improvement. By Caching the Material itself, we have reduced the Garbage Collection from the Update method from 1.8K initially to only 86B, roughly 95% less.
New Objects:
When it comes to using the New keyword, we need to look a little deeper. When it comes to things like new Color or new Vector3, we don’t need to worry as these are Value types which are stored on the Stack and don’t cause Garbage Collection. On the other side we have Reference types. These are stored on the Heap and cause Garbage Collection allocation and wasting/using more memory. Reference types consist of Strings, Arrays and Classes.
That doesn’t mean that you should just create a load of new Value types as it may not create Garbage Collection but it can slow down game performance needlessly calling unnecessary new Value types. Instead you can create a struct and reuse it rather than multiple calls.
Coroutines:
We covered Coroutines in a previous post but we can review it here again briefly. It kind of goes well with the previous points. When using coroutines the use of yield returns can cause extra Garbage Collection and slow downs in performance. It’s best to cache a specific yield and then call the cached yield rather than creating new yields each call. For more on this, you can find my post here.
RayCasts:
Unity has made it easier to use RayCasts with better performance and with reduced Garbage Collection. This is done by using Non Allocation methods.
These can be used using the Physics.RayCastNonAlloc and Physics.SphereCastNonAlloc.
Also when using the Physics.Raycast usually we cast from Camera.Main. This is not performant. As shown in the previous points caching is your friend so cache the Camera.Main to save on memory usage as Camera is a class and therefore uses the Heap and creates more Garbage Collection Allocation.
Lists and Arrays:
When using Arrays, any action you perform that modifies it, causes the Array to be recreated. Lists are easier and more efficient to index and use. Lists can be cleared very easily using a List.clear. Use Lists instead of Arrays where possible.
Structs and Classes:
Structs don’t get added to the Heap and therefore don’t cause Garbage Collection. Be careful in usage though as there is a general rule of thumb that more than 5 variables use a Class and 5 or less use a Struct.