Unity performance and empty functions

We’re busy here at Horse Drawn HQ working on our first mobile and tablet game. We’ve still got a bit of a way to go, but I thought I’d do some profiling of our game on my iPhone 4 using Unity’s built-in profiler to see where we were at. I was surprised to see an update function showing up for each of the main playable game objects and taking anywhere from 4ms to 9ms! This was the function:

void Update()
{
    Vector4 campos = Camera.main.transform.position;
    campos = renderer.worldToLocalMatrix.MultiplyPoint(campos);
    renderer.material.SetVector("_modelSpaceCameraPos", campos);
}

This code transforms the camera position into model space and then sets the result as a shader constant. Okay, so maybe it’s just that slow – it’s being done for up to 125 objects every frame. I’m not too worried about it at the moment though, because that shader constant is only used by a particular material which we’re currently not planning on shipping with. However, we want to be able to switch materials, so I changed the code to the following:

void Update()
{
    if (m_updateCamPos)
    {
        Vector4 campos = Camera.main.transform.position;
        campos = renderer.worldToLocalMatrix.MultiplyPoint(campos);
        renderer.material.SetVector("_modelSpaceCameraPos", campos);
    }
}

m_updateCamPos is usually false, and true only when we use the material that needs it. At the moment the game isn’t using those materials so this is always false. This helped a lot; the function still shows up but now only at around 0.1ms or 0.2ms. Unfortunately it still spikes at 2ms or 3ms (once even at 33ms)! This still isn’t good enough. I don’t want an empty function to show up at all (note that it is possible that the empty function will be optimised out in a final build, but you can’t profile a final build in Unity).

It turns out that Unity actually calls the MonoBehaviour methods that you ‘override’ using reflection; Update, Start, et al are not actually virtual methods. You can tell because you never have to use the ‘override’ or ‘new’ keywords when defining those functions, which C# requires if you are introducing a new implementation of an inherited method, and also Start and Awake can have a return type of void OR IEnumerator but C# doesn’t support covariant return types. To be a bit more concrete about it, if we bust open MonoBehaviour in Visual Studio or ILDasm we see this (note: attributes and comments removed for brevity):

namespace UnityEngine
{
    public class MonoBehaviour : Behaviour
    {
        public MonoBehaviour();
        public bool useGUILayout { get; set; }
        public void CancelInvoke();
        public void CancelInvoke(string methodName);
        public void Invoke(string methodName, float time);
        public void InvokeRepeating(string methodName, float time, float repeatRate);
        public bool IsInvoking();
        public bool IsInvoking(string methodName);
        public static void print(object message);
        public Coroutine StartCoroutine(IEnumerator routine);
        public Coroutine StartCoroutine(string methodName);
        public Coroutine StartCoroutine(string methodName, object value);
        public Coroutine StartCoroutine_Auto(IEnumerator routine);
        public void StopAllCoroutines();
        public void StopCoroutine(string methodName);
    }
}

If you dig deeper down the inheritance hierarchy (MonoBehaviour -> Behaviour -> Component -> Object) you can see that there are no virtual functions at all. So Unity is using reflection to call these functions, and reflection is not cheap! I would imagine that Unity are doing things like caching the MethodInfo objects, or even converting to delegates which are apparently faster. But I think the best thing to do is to write your code in such a way that empty update functions don’t exist. This is commonly mentioned in relation to the OnGUI function but it seems that it applies to all the common MonoBehaviour functions. In our case, I will be removing the method entirely for now, but if and when we need this functionality I will create a derived class that implements update, so that the common case is not penalised.

Leave a Reply

  • (will not be published)

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Current day month ye@r *