Monday, May 07, 2012

Micro Optimisation in Unity3D

NGUI caches the transform component in a member variable called mTrans, to optimise speed of access. I think this is UGLY, so I decided to do some benchmarks to see if the ugliness is worth the performance increase.

In short, the answer is NO.

My tests showed that using a local variable is always as fast as using a member variable, and usually faster.

Here are some typical results.

Cached: Time elapsed: 00:00:04.8086550
Uncached: Time elapsed: 00:00:09.1058490
Local: Time elapsed: 00:00:04.7870590

They show that caching and accessing the target reference is twice as fast as just using the reference. They also show that assigning to a local variable and using that variable for access is usually even faster than a member variable.

This is the code I used for benchmarking.

using UnityEngine;
using System.Collections;

public class Benchmark : MonoBehaviour
{
    Transform cachedTransform;
    
    void Start () {
        cachedTransform = transform;
        TestCached();    
        TestUncached();
        TestLocal();
    }
    
    void TestUncached()
    {
        var stopwatch = new System.Diagnostics.Stopwatch ();
        stopwatch.Start ();
        for (int i = 0; i < 100000000; i++) {
            var p = transform.position;
        }
        stopwatch.Stop ();
        Debug.Log (string.Format("Uncached: Time elapsed: {0}", stopwatch.Elapsed));
    }
    
    void TestCached()
    {
        var stopwatch = new System.Diagnostics.Stopwatch ();
        stopwatch.Start ();
        for (int i = 0; i < 100000000; i++) {
            var p = cachedTransform.position;
        }
        stopwatch.Stop ();
        Debug.Log (string.Format("Cached: Time elapsed: {0}", stopwatch.Elapsed));
    }
    
    void TestLocal()
    {
        var stopwatch = new System.Diagnostics.Stopwatch ();
        stopwatch.Start ();
        var t = transform;
        for (int i = 0; i < 100000000; i++) {
            var p = t.position;
        }
        stopwatch.Stop ();
        Debug.Log (string.Format("Local: Time elapsed: {0}", stopwatch.Elapsed));
    }
}

10 comments:

Nevermind said...

You missed GetComponent() alternative. Which is WAY slower than others:

GC: Time elapsed: 00:01:53.8888645

Still, it should be mentioned for the sake of completeness. I wonder, though, what does GameObject.transform do that is so much faster than GetComponent, but still twice as slow as member access.

Jordi Linares said...

I think there is a wrong approach in this test. The results are somehow obvious. I think that the experiment should compare the 3 approaches when using the cached transform in an Update(), where the difference could be obvious if we have to get the reference to the transform at everyframe. Making the test in the Start() proves nothing to me.

Simon Wittber said...

No, you don't want to do that. I am testing the speed of accessing variables in different ways from inside a function. To test this single thing, I isolate it and run it inside a minimal loop.

Running in the update function would effectively test everything Unity does per frame, not just a single variable access.

Jordi Linares said...

That's right and I understand the results. Caching is worth anyway in front of a general script with Update functions, where don't caching could be noticed.

Jedy said...

Interesting benchmark.

I had some doubts at first but indeed you are correct. The local variable approach is the better option if the cached value is used repeatedly.

Anyhow - after a set of benchmarks on my own ( using your code as a basis ) the clear winner with a few loops is the cached transform.
With just a few loops - 1-5 the cached transform is significantly faster ( 35% ). When the loops increase the percentage drops in favor of the local variable ( 30% at 10, the local gets 20% better score on 5000 iterations ).

Even though you results are perfectly correct for a heavy amount of data accessors - in the common case the cached transform is the fastest option.

Simon Wittber said...

Hmm, when I change my test to run 5 loops only, I get these figures, which show local access still comes out on top. Can you post your tests so I can compare?

Cached: Time elapsed: 00:00:00.0002060
Uncached: Time elapsed: 00:00:00.0000050
Local: Time elapsed: 00:00:00.0000050

Jedy said...

Oh yeah, one thing I forgot to mention - the test ran first is delayed big time.
Anyhow here are some numbers :
Cached: Time elapsed: 00:00:00.0002370
Uncached: Time elapsed: 00:00:00.0000052
Local: Time elapsed: 00:00:00.0000046
All good and shining for the Local variables. But after a quick reordering here is what you get :
Uncached: Time elapsed: 00:00:00.0002386
Local: Time elapsed: 00:00:00.0000051
Cached: Time elapsed: 00:00:00.0000031
Or you get :
Local: Time elapsed: 00:00:00.0002853
Cached: Time elapsed: 00:00:00.0000025
Uncached: Time elapsed: 00:00:00.0000061
There is no obvious reason for this random delay happening on the first test. I've tried delaying the action itself and a couple of other approaches but the issue persists. My guess is that it has something to do with the internal stuff of the Unity Editor.

PS: Your captcha hates me :D

Anonymous said...

Doesn't using a local variable add to gc hiccups though?

ken arroyo darkeye said...
This comment has been removed by the author.
ken arroyo darkeye said...

class MyBehaviour : MonoBehaviour {
new internal Transform transform;

void Awake () {
transform = this.GetComponent<Transform>();
}
}

and smile.

Popular Posts