0

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.

Milan Egon Votrubec
  • 3,696
  • 2
  • 10
  • 24
blackTree
  • 97
  • 6
  • 4
    Random person or ChatGPT saying something does not make it true... Please [edit] the question why *you* believe "this check is cost expensive". – Alexei Levenkov Jan 05 '23 at 01:50
  • If you need to check for the existence of an object in your scene then perhaps you're doing something wrong. If you're worried about a script loading, you might benefit from putting your script into an IEnumerator or changing your script execution order. – Display name Jan 05 '23 at 11:49
  • What do you mean by "cost expensive"? Why are you concerned if you tested a comparison and found no difference in performance? – TylerH Jan 05 '23 at 17:34
  • @TylerH "cost expensive" mean Have much performance than other. and yes i can't find difference in test – blackTree Jan 06 '23 at 04:31

3 Answers3

4

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.

Milan Egon Votrubec
  • 3,696
  • 2
  • 10
  • 24
  • 1
    Destroy only places the item in a list of object to be destroyed at the end of the frame. If you need the object to be null right away, DestroyImmediate is the one to use. – Everts Jan 05 '23 at 08:59
  • @Everts You’re correct, though quoting the docs “You are strongly recommended to use Destroy instead. This function should only be used when writing editor code…” While you’re correct that the object is destroyed in this frame, aside from the explicit warning from Unity themselves, the `go` reference is still pointing to the old object and using ‘==' is still required, and we come back to the original question of how costly ‘==‘ is, and why. – Milan Egon Votrubec Jan 05 '23 at 09:23
  • I see no use case for Instantiate and Destroy. That will definitely lag your game. Instead, instantiate into a list and just turn the object off when you're done. Then check the list for inactive objects. Using Destroy is well... destructive and shouldn't be paired with Instantiate. Furthermore, it is nice to know your machine ran the check 10 million times to show meaningful differences but without specs, your results are almost meaningless. – Display name Jan 05 '23 at 11:46
2

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

Display name
  • 413
  • 3
  • 14
  • Both `==` and the implicit `bool` operator use the same underlying native code -> I claim there isn't a difference in performance. Note that there is also a huge difference between the `UnityEngine.Object` returning `true` for `== null` and the underlying `System.Object` actually being `null` .. your code snippet may behave completely unexpected and is therefore misleading – derHugo Jan 05 '23 at 11:20
1

There is a custom implementation of == for UnityEngine.Object. Mainly because we don't know for sure when exactly the Garbage Collector will erase the UnityEngine.Object at the c# level after it has been destroyed in the underlying c++ engine level Unity is actually running on.

Regarding performance

If you look into the source code for both ==

public static bool operator==(Object x, Object y) { return CompareBaseObjects(x, y); }

and the implicit bool operator

public static implicit operator bool(Object exists)
{
    return !CompareBaseObjects(exists, null);
}

you see that basically both under the hood use the exact same method and finally link into the underlying c++ method. The implicit bool basically equals

return theObject != null;

just written differently ;)

I would boldly claim there shouldn't be any difference in performance at all - but am also happy to be proven wrong ^^ - and I would say it comes more down to preferences. Some people prefer the explicit written down == null for readability, I personally prefer the bool operator.


However

One main issue with that == operator comes more when using the ?. or ?? operators as these work directly on the System.Object level and totally bypass Unity's custom implementations. (See further information e.g. Why does C# null-conditional operator not work with Unity serializable variables?)

This can be a huge pitfall when using != null and the IDE suggesting to rather use ?. and the developer blindly following it!

I don't know Visual Studio nowadays but Rider e.g. explicitly handles UnityEngine.Object different and always suggests to rather use the bool operator instead of == null and also warns about the above mentioned pitfall of bypassing the native null check

derHugo
  • 83,094
  • 9
  • 75
  • 115