Home > database >  Is it cost expensive to use "if ( gameobject == null )"?
Is it cost expensive to use "if ( gameobject == null )"?

Time:01-05

I usually use if ( gameobject == null ) but I heard this is very expensive in game.

I checked if ( gameobject == null ) and if ( boolvalue == false ), but cannot notice any difference.

Could you explain why this check is cost expensive? Thank you in advance.

CodePudding user response:

An answer for the check's expensiveness is as follows:

"This transition to native code can be expensive, as Unity will perform lookups and validation to convert the script reference to the native reference. While small, the cost of comparing a Unity object to null is much more expensive than a comparison with a plain C# class, and should be avoided inside a performance critical context, and inside loops."

Despite this answer, I've heard many fellow developers say that the checks aren't very intensive at all, unless if spammed or used en masse. You should only really worry about it if your performance on your project begins to dip.

However, a less-intensive version of the code is as follows:

if (!System.Object.ReferenceEquals(gameObject, null)) {

// do stuff with gameObject }

Links to both answers and further explanation below:

https://github.com/JetBrains/resharper-unity/wiki/Avoid-null-comparisons-against-UnityEngine.Object-subclasses

https://subscription.packtpub.com/book/game development/9781785884580/2/ch02lvl1sec23/faster-gameobject-null-reference-checks

CodePudding user response:

Generally, questions like this could fall into the "opinion" category, but in this case, we can quantify an answer by running a small test.

There's actually a few things going on here, and there's a deeper issue going on.

I'll start with the test code:

public class Test : MonoBehaviour
{
    public GameObject prefab;

    private IEnumerator Start ( )
    {
        var go = Instantiate(prefab);
        Destroy(go);
        Debug.Log ( $"go is null: {go is null}. go == null: {go == null}." ); // False False.
        yield return null; // skip to next frame
        Debug.Log ( $"go is null: {go is null}. go == null {go == null}." ); // False True.
        yield return null; // skip to next frame

        var sw = new Stopwatch();
        sw.Start ( );
        for ( var i = 0; i < 10_000_000;   i )
            _ = go is null;
        sw.Stop ( );
        Debug.Log ( $"go is null: {sw.ElapsedMilliseconds} ms" ); // Test machine : ~10 ms

        sw.Reset ( );

        sw.Start ( );
        for ( var i = 0; i < 10_000_000;   i )
            _ = go == null;
        sw.Stop ( );
        Debug.Log ( $"go == null: {sw.ElapsedMilliseconds} ms" ); // Test machine : ~4000 ms

        yield return null; // skip to next frame
        Debug.Log ( $"go is null: {go is null}. go == null {go == null}." ); // STILL False True.

#if UNITY_EDITOR
        EditorApplication.ExitPlaymode ( );
#endif
    }
}

So, the first thing to notice is that after we instantiate and immediately destroy an object, in that particular frame, the object isn't 'destroyed' in either the managed or unmanaged 'memory', as indicated by the "False False".

However, once we skip to the next frame, you'll notice that the unmanaged (Unity backend processing) has marked the object as being null. There's an issue though, the managed code still sees that go referencing an object, which it kind of is, just not a valid backend object anymore. This is why the next debug line will indicate "False True" for the null checks.

The reason I'm bringing this up is because some developers mistakenly think that short-circuiting and bypassing the overloaded '==' operator is fine. It works most of the time, right? Well, clearly it doesn't, as demonstrated here.

So, now to the timing of using '=='. There's a HUGE difference between using object == null and object is null. As you can see, in the order of 400x times slower using '=='. That's because Unity has overloaded the '==' operator to not just check if a reference is null, but also if the underlying backend object is null too. This is a costly process (comparatively), and is why people say to avoid it. BUT, just because something else is faster, does NOT make it any better, and in this case, using the faster check can lead to bugs.

The solution would be to keep track of the managed reference yourself. To do that, you would do:

var go = Instantiate(prefab);
Destroy(go);
go = null;
Debug.Log ( $"go is null: {go is null}. go == null: {go == null}." ); // True True.

If every time you destroy a Unity object you also set the reference to null, you can then safely check the null status of a reference using object is null which is significantly faster that object == null.

Now, having said all of that!

Take note that to get any meaningful millisecond values in the StopWatch, I had to loop through the checks 10 MILLION times! Which then brings me to my final point... there are PLENTY of other places to eke out performance for your game than worrying about whether '==' is expensive or not. Use it, and focus on the rest of your code.

  • Related