Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the benefit of one single MonoBehavior class?

Tags:

c#

unity3d

Sometimes I see how Unity programmers use one script that inherits MonoBehavior for almost the entire project. The so-called "Update Managers". All scripts are subscribed to the queue for execution, and the manager runs all functions, and after execution removes them from the queue. Does this really have any effect on optimization?

like image 262
Spasibo Avatar asked Mar 31 '21 18:03

Spasibo


2 Answers

This was one of the optimization technique I analyzed in my thesis.

The Unity engine has a Messaging system which allows the developers to define methods that will be called by an internal system based on their functionalities. One of the most commonly used Messages is the Update message. Unity is inspecting every MonoBehaviour the first time the type is accessed (independently from the scripting backend (mono, il2cpp)) and checks if any of the Message methods are defined. If a Message method is defined then the engine will cache this information. Then if an instance of this type is instantiated then the engine will add it to the appropriate list and will call the method whenever it should. This is also the key reason why Unity does not care about the visibility of our Message method, and that they are not called in a deterministic order.

public class Example1 : MonoBehaviour
{
    private void Update() { }
}
public class Example2: MonoBehaviour
{
    public void Update() { }
}

Both of the above achieves the same results but god knows which will be called first.

One of the main problem with this approach is that every time the engine calls a Message method an interop call (a call from c/c++ side to the managed c# side) has to happen. In case of Update luckily no marshaling is needed so this overhead is a bit smaller. However, if our game handles thousand or tens of thousands of objects which all have a script requiring a Message call then this overhead can be significant. A solution to this is to avoid interop calls. A good approach to this is behavior grouping. If we have a MonoBehaviour that is attached to a huge number of GameObjects we can cut the number of interop calls to just one by introducing an update manager. Since the update manager is also a managed object running managed code the only interop call will happen between the update manager’s Update Message and the Unity engine’s internal Message handler. We have to note the fact that this optimization technique is only relevant in large scale projects, and the frame time saved via this technique is more impactful when using the Mono scripting backend. (Remember IL2CPP transpiles to C++).

test The above picture illustrates the difference between the two methods.

Let's do a benchmark with Unity's performance tools. The benchmark will spawn 10 000 gameobjects each with a mover script which moves these cubes up and down.

test Illustration of the example scene using the traditional method.

Now let's see the results of the bechmarks. enter image description here

Not surprisingly IL2CPP leading the competition by far however it’s still interesting that the Update Manager is still twice as fast as the traditional way. If we profiled the execution of the Traditional method’s IL2CPP build we would find many Unity specific calls like check if the GameObject exists before invoking a component method etc. and these would explain the longer execution time. We can also make the conclusion that IL2CPP in this case is far faster than Mono, usually around twice as fast. The benchmark ran for one minute prior to a 5 seconds warmup and both scripting backends had the ideal compiler setting.

like image 75
Menyus Avatar answered Nov 03 '22 00:11

Menyus


Based on this article which your link has a link to, having an "update manager" does indeed have a positive impact on performance when compared to using Unity's Update method. The gist is that if you implement Update in one of your classes, Unity has some additional overhead in calling Update; it doesn't run quite as fast as calling a method yourself, such as saying myObject.Update(). So if you're calling Unity's Update on 10,000 game objects per frame, that additional overhead becomes noticeable.

If you explicitly call your update-type methods from a "manager" class -- rather than letting Unity call the "magic" Update methods -- then you can avoid the additional overhead that comes with using "magic" methods.

But keep in mind that the performance penalty of using Update will only be noticeable if you have a lot of game objects in your scene that all implement Update. Game objects in your scene that don't implement Update won't have an effect. It would be good practice to remove the Update method that Unity adds to all new scripts if you're not using it though.

In short, unless you have a huge number of objects in your scene and you are running into performance problems, I wouldn't worry about it.

like image 22
Ben Rubin Avatar answered Nov 03 '22 00:11

Ben Rubin