What if I told you that Unity can be wasting a lot of CPU performance just by calling your MonoBehaviour functions? It doesn’t really matter what your scripts are doing. If you have hundreds or thousands of them, you should be aware that there’s a new field of optimization.
Magic methods
MonoBehaviour functions calls are slow. And I’m talking about functions like Update(), LateUpdate(), OnRender() etc. They are so-called magic methods, and if you’re familiar with object-oriented programming languages, this concept looks like calling a method using reflection mechanism (reflection enables method calls even if you don’t know the interface). Reflection calls are expensive, so Unity does everything that is possible to cache any operations, so the set of CPU instructions needed to call a magic method each frame could be minimal. But it can still be slow, very slow…
Why is it so slow?
I’m not gonna talk about the details (but if you really want to read about the details, look at the end of this article for the links), so just imagine that Unity tries to be as flexible and easy to use as possible. Making something easily costs CPU power because the engine cannot make any assumptions about your game and it needs to do a bunch of checks to call your magic functions on the right objects, in the right order, and to not crash in the meantime.
Can it become faster?
Oh this is my favorite part. Yes! It can! How? You have to take the responsibility of calling Update() function by defining your own function and calling it from a manager. This way, you’re taking responsibility for updating your objects. How much faster it can become? Well, it depends on the platform. Let me use the measurements done by Valentin Simonov on official Unity blog:
Here you see that the difference can be worth the time. This is a measurement of calling Update() 10000 times.
Writing a manager
I will present a fairy simple example of a manager called BoxManager that is managing BoxManaged scripts. Manager has two responsibilities:
- Keeping the list of managed objects updated
- Calling update-like functions on managed objects when manager Update() is called.
The code may look like this:
using UnityEngine; using System.Collections; using System.Collections.Generic; public class BoxManager : MonoBehaviour { public static BoxManager Instance { get; private set; } public List<BoxManaged> _managedBoxes = new List<BoxManaged>(); void Awake() { Instance = this; } void Update() { // update objects here } public void Register(BoxManaged box) { _managedBoxes.Add(box); } public void Unregister(BoxManaged box) { _managedBoxes.Remove(box); } }
As you can see, it’s really simple. Before implementing Update() function let’s take a look at BoxManaged.cs.
using UnityEngine; public class BoxManaged : MonoBehaviour { private Vector3 _position; private Transform _transform; void OnEnable() { BoxManager.Instance.Register(this); } void OnDisable() { BoxManager.Instance.Unregister(this); } public void ManagedUpdate() { // do what you normally do in Update here } }
It registers itself when enabled and de-registers when disabled. Fair enough. ManagedUpdate() function is a function that will replace Update() magic function. Let’s implement BoxManager.Update(), so it will be able to call all BoxManaged.ManagedUpdate() at once.
void Update() { for (int i = 0; i < _managedBoxes.Count; ++i) { _managedBoxes[i].ManagedUpdate(); } }
And that’s it! Really! Now in ManagedUpdate() you can do everything you would normally do in the Update() function.
Please note that I did not use foreach for iterations. Firstly, because it’s generating small amount garbage Unity’s version of Mono. Secondly, because it simply seems to be slower.
Should I care?
It depends on what kind of game are you creating and what is the target platform. Ask yourself a question – do you have many MonoBehaviour objects with Update() calls? It doesn’t necessarily need to be Update(), it can be anything that it is invoked with each frame. Then, if you’re targeting mobiles, it’s definitely worth to try! Targeting standalones? It’s still something you may consider, especially if you’re planing to have huge amount of objects.
Sometimes you may need a manager even if you’re have a relatively small amount of objects. On iOS there was (I don’t know if it has been fixed or not) a problem with OnRender() function. Having it on 30-40 objects could decrease the game’s performance twice! The solution? A manager like the one presented above, but instead of calling Update() it should be calling OnRender() code. Yes, it will work!
Please keep in mind that this is one of many optimization strategies that you can use. Yet this one is quite hidden – unless you know about it, you will have a hard time to find about this one. That’s the reason why this article has been brought to life.
References:
https://blogs.unity3d.com/2015/12/23/1k-update-calls/