Test Outcome

In short; C#'s delegates and actions have very little invocation cost and generate no garbage on Invoke(). However, UnityEvents are 10x more expensive in CPU time and generate tons of garbage on each Invoke(). Always use C# delegates/actions and not UnityEvents.

  • A C# delegate/action invocation is about 10-20ms longer than a direct call to a method at 10 milion iterations, so the overhead is very negligible.
  • C# delegate/action invocations do not generate garbage, but UnityEvent invocations generate tons of garbage ( http://jacksondunstan.com/articles/3335 )
  • There is no real performance differance between delegates, actions, and UnityActions
  • There is a huge performance difference between UnityEvents and delegates/actions/UnityActions
  • The overhead of the delegates does not scale with the cost of the method itself, but it does grow with the amount of parameters
  • There is absolutely no performance difference between myAction() and myAction.Invoke(). They compile to the same bytecode

The Test

public class MyObjectEvent : UnityEvent<GameObject>
{
}

public class DelegatesTest : MonoBehaviour
{
    private delegate void TestDelegateVoid();
    private TestDelegateVoid testDelegateVoid;
    private Action testActionVoid;
    private UnityAction testUnityActionVoid;
    private UnityEvent testUnityEventVoid;

    private delegate void TestDelegateObject(GameObject anObject);
    private TestDelegateObject testDelegateObject;
    private Action<GameObject> testActionObject;
    private UnityAction<GameObject> testUnityActionObject;
    private MyObjectEvent testUnityEventObject;

    public const int iterations = 10000000;

    void Start ()
    {
        testUnityEventVoid = new UnityEvent();
        testUnityEventObject = new MyObjectEvent();

        testDelegateVoid += TestMethodVoid;
        testActionVoid += TestMethodVoid;
        testUnityActionVoid += TestMethodVoid;
        testUnityEventVoid.AddListener(TestMethodVoid);

        testDelegateObject += TestMethodObject;
        testActionObject += TestMethodObject;
        testUnityActionObject += TestMethodObject;
        testUnityEventObject.AddListener(TestMethodObject);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Stopwatch watch = new Stopwatch();

            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; i++)
            {
                TestMethodVoid();
            }
            watch.Stop();
            UnityEngine.Debug.Log("Void direct call " + watch.ElapsedMilliseconds.ToString());

            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; i++)
            {
                testDelegateVoid();
            }
            watch.Stop();
            UnityEngine.Debug.Log("Void delegate " + watch.ElapsedMilliseconds.ToString());

            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; i++)
            {
                testActionVoid();
            }
            watch.Stop();
            UnityEngine.Debug.Log("Void action " + watch.ElapsedMilliseconds.ToString());

            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; i++)
            {
                testUnityActionVoid();
            }
            watch.Stop();
            UnityEngine.Debug.Log("Void UnityAction " + watch.ElapsedMilliseconds.ToString());

            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; i++)
            {
                testUnityEventVoid.Invoke();
            }
            watch.Stop();
            UnityEngine.Debug.Log("Void UnityEvent " + watch.ElapsedMilliseconds.ToString());

            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; i++)
            {
                TestMethodObject(this.gameObject);
            }
            watch.Stop();
            UnityEngine.Debug.Log("Object direct call " + watch.ElapsedMilliseconds.ToString());

            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; i++)
            {
                testDelegateObject(this.gameObject);
            }
            watch.Stop();
            UnityEngine.Debug.Log("Object delegate " + watch.ElapsedMilliseconds.ToString());

            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; i++)
            {
                testActionObject(this.gameObject);
            }
            watch.Stop();
            UnityEngine.Debug.Log("Object action " + watch.ElapsedMilliseconds.ToString());

            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; i++)
            {
                testUnityActionObject(this.gameObject);
            }
            watch.Stop();
            UnityEngine.Debug.Log("Object UnityAction " + watch.ElapsedMilliseconds.ToString());

            watch.Reset();
            watch.Start();
            for (int i = 0; i < iterations; i++)
            {
                testUnityEventObject.Invoke(this.gameObject);
            }
            watch.Stop();
            UnityEngine.Debug.Log("Object UnityEvent " + watch.ElapsedMilliseconds.ToString());
        }
    }

    public void TestMethodVoid()
    {

    }

    public void TestMethodObject(GameObject anObject)
    {

    }
}

And here is the same test but with some complex math inside the called methods:

results matching ""

    No results matching ""