Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Optimizing null check: != null vs bool + out functions

Discussion in 'Scripting' started by Denggoon, Jul 7, 2017.

  1. Denggoon

    Denggoon

    Joined:
    Mar 26, 2013
    Posts:
    6
    Hi,

    while profiling my script, I've noticed that Object.op_Inequality does quite an effect on performance,

    so I've wondered whether changing all !=null checks to boolean + out Functions would improve performance.

    The results were as follow:
    !=null performance x10000 times: 00:00:00.0414194
    bool and out func performance x10000 times: 00:00:00.0465496

    != null performance performance: 00:00:00.0000234
    bool and out func performance: 00:00:00.0000102

    when comparing !=null and bool and out func functions, latter was faster,
    and when comparing 10000 times, != null check was faster.

    Here are some code for test results, and I would like to hear some advices or ideas whether this was a fair test.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Diagnostics;
    5.  
    6. public class NotNullTest : MonoBehaviour {
    7.  
    8.     public List<GameObject> sampleList;
    9.     // Use this for initialization
    10.     void Start () {
    11.  
    12.         GameObject g;
    13.  
    14.         Stopwatch sw = new Stopwatch();
    15.         sw.Reset();
    16.         sw.Start();
    17.  
    18.         for (int i = 0; i < 10000; i++)
    19.         {
    20.             g = NotNull("test");
    21.             if (g != null)
    22.             {
    23.  
    24.             }
    25.         }
    26.         sw.Stop();
    27.        UnityEngine.Debug.Log("!=null performance x10000 times: " + sw.Elapsed.ToString());
    28.  
    29.  
    30.         sw.Reset();
    31.         sw.Start();
    32.  
    33.         for (int i = 0; i < 10000; i++)
    34.         {
    35.             if (NotNullOutFunc("test", out g))
    36.             {
    37.  
    38.             }
    39.         }
    40.         sw.Stop();
    41.         UnityEngine.Debug.Log("bool and out func performance x10000 times: " + sw.Elapsed.ToString());
    42.  
    43.         sw.Reset();
    44.         sw.Start();
    45.  
    46.         g = NotNull("test");
    47.         if (g != null)
    48.         {
    49.  
    50.         }
    51.  
    52.         sw.Stop();
    53.         UnityEngine.Debug.Log("!= null performance performance: " + sw.Elapsed.ToString());
    54.  
    55.         sw.Reset();
    56.         sw.Start();
    57.  
    58.         if (NotNullOutFunc("test", out g))
    59.         {
    60.  
    61.         }
    62.  
    63.         sw.Stop();
    64.         UnityEngine.Debug.Log("bool and out func performance: " + sw.Elapsed.ToString());
    65.     }
    66.  
    67.     GameObject NotNull(string name)
    68.     {
    69.         for (int i = 0; i < sampleList.Count; i++)
    70.         {
    71.             GameObject n = sampleList[i];
    72.             if (n.name.Contains(name))
    73.             {
    74.                 return n;
    75.             }
    76.         }
    77.  
    78.         return null;
    79.     }
    80.  
    81.     bool NotNullOutFunc(string name, out GameObject obj)
    82.     {
    83.         for (int i = 0; i < sampleList.Count; i++)
    84.         {
    85.             GameObject n = sampleList[i];
    86.             if (n.name.Contains(name))
    87.             {
    88.                 obj = n;
    89.                 return true;
    90.             }
    91.         }
    92.  
    93.         obj = null;
    94.         return true;
    95.     }
    96. }
    Thanks in advance.
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,199
    NotNullOutFunc returns a bool, so you're just getting a raw branching, which is fast.

    NotNull returns a GameObject, and then the implicit UnityEngine.Object->bool cast kicks in. That cast involves a call to UnityEngine.==, which checks if the object is null or if it's destroyed, which means that it needs to visit the C++ engine.

    So the call to NotNullOutFunc resolves pretty much to:

    Code (csharp):
    1. bool result = NotNullOutFunc("test", out g);
    2. if(result)
    3. {
    4.     ...
    5. }
    while NotNull resolves to:

    Code (csharp):
    1.  
    2. GameObject foundGo = NotNull("test");
    3.  
    4. if(foundGo != null)
    5. {
    6.     ...
    7. }
    That last part there looks fast, but != is overloaded for UnityEngine.Object. The decompiled sources looks like this:

    Code (csharp):
    1.  
    2. //This is the Op_Inequality you're seeing costing a bunch
    3. public static bool operator !=(Object x, Object y)
    4. {
    5.   return !Object.CompareBaseObjects(x, y);
    6. }
    7.  
    8. private static bool CompareBaseObjects(Object lhs, Object rhs)
    9. {
    10.   bool flag1 = (object) lhs == null;
    11.   bool flag2 = (object) rhs == null;
    12.   if (flag2 && flag1)
    13.     return true;
    14.   if (flag2)
    15.     return !Object.IsNativeObjectAlive(lhs);
    16.   if (flag1)
    17.     return !Object.IsNativeObjectAlive(rhs);
    18.   return lhs.m_InstanceID == rhs.m_InstanceID;
    19. }
    20.  
    21. private static bool IsNativeObjectAlive(Object o)
    22. {
    23.   if (o.GetCachedPtr() != IntPtr.Zero)
    24.     return true;
    25.   if (o is MonoBehaviour || o is ScriptableObject)
    26.     return false;
    27.   return Object.DoesObjectWithInstanceIDExist(o.GetInstanceID());
    28. }
    29.  
    30. [ThreadAndSerializationSafe]
    31. [GeneratedByOldBindingsGenerator]
    32. [MethodImpl(MethodImplOptions.InternalCall)]
    33. internal static extern bool DoesObjectWithInstanceIDExist(int instanceID);
    34.  
    So checking if a UnityEngine.Object != null is actually a ton of work compared to checking if any other kind of object is != null.
     
    nixalott, glenneroo, lasiaylo and 8 others like this.
  3. Denggoon

    Denggoon

    Joined:
    Mar 26, 2013
    Posts:
    6
    Thank you, Baste, it really seems that doing != null on UnityEngine.Object does a lot of work!

    I have a little more question to ask -

    What if I decide to change all of my codes which of those are using != null with UnityEngine.Object in my mobile project, in terms of script optimization - would that be a meaningful work?

    I'm still in search of wisdom in terms of optimization - so a little advice would be great.
     
    Gametyme likes this.
  4. Denggoon

    Denggoon

    Joined:
    Mar 26, 2013
    Posts:
    6
    Actually, while doing more research, I've found a faster method of null check: which is System.Object.ReferencEquals method.

    !=null performance: 00:00:00.0000019
    ReferenceEqausl null performance: 00:00:00.0000016

    !=null performance X10000: 00:00:00.0002576
    ReferenceEqausl null performance X10000: 00:00:00.0001070

    Test code as follows:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Diagnostics;
    4.  
    5. public class NotNullVsReferenceEquals : MonoBehaviour {
    6.  
    7.     GameObject testObj;
    8.     // Use this for initialization
    9.     void Start() {
    10.  
    11.         Stopwatch sw = new Stopwatch();
    12.  
    13.         sw.Reset();
    14.         sw.Start();
    15.  
    16.         if (testObj != null)
    17.         {
    18.  
    19.         }
    20.  
    21.         sw.Stop();
    22.         UnityEngine.Debug.Log("!=null performance: " + sw.Elapsed.ToString());
    23.  
    24.         sw.Reset();
    25.         sw.Start();
    26.  
    27.         if (!System.Object.ReferenceEquals(testObj, null))
    28.         {
    29.  
    30.         }
    31.  
    32.         sw.Stop();
    33.         UnityEngine.Debug.Log("ReferenceEqausl null performance: " + sw.Elapsed.ToString());
    34.  
    35.         sw.Reset();
    36.         sw.Start();
    37.  
    38.         for (int i = 0; i < 10000; i++)
    39.         {
    40.             if (testObj != null)
    41.             {
    42.  
    43.             }
    44.         }
    45.  
    46.         sw.Stop();
    47.         UnityEngine.Debug.Log("!=null performance X10000: " + sw.Elapsed.ToString());
    48.  
    49.         sw.Reset();
    50.         sw.Start();
    51.  
    52.         for (int i = 0; i < 10000; i++)
    53.         {
    54.             if (!System.Object.ReferenceEquals(testObj, null))
    55.             {
    56.  
    57.             }
    58.         }
    59.  
    60.         sw.Stop();
    61.         UnityEngine.Debug.Log("ReferenceEqausl null performance X10000: " + sw.Elapsed.ToString());
    62.     }
     
    guycalledfrank and Shawzee like this.
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,199
    Note that if you are working with UnityEngine.Object objects, you often want the slowness of the == null check, since that also checks if the object has been destroyed.

    So if you do something like:

    Code (csharp):
    1. void OnTriggerEnter(Collider other) {
    2.     if(other.CompareTag("Player") {
    3.         chaseTarget = other.transform;
    4.     }
    5. }
    6.  
    7. void Update() {
    8.     if(!ReferenceEquals(chaseTarget, null) {
    9.         transform.position += (chaseTarget.position - transform.position).normalized * speed * Time.deltaTime;
    10.     }
    11. }
    If the chaseTarget gets destroyed, the ReferenceEquals(chaseTarget, null) check will still return false, and the Update will throw a MissingReferenceException on chaseTarget.position. So in that case, you don't want to go through object's !=.


    (object) obj == null and ReferenceEquals(obj, null) results in the same thing logically. I'm not sure which one if faster, you'd have to test to be sure.
     
  6. RMGK

    RMGK

    Joined:
    Sep 30, 2011
    Posts:
    75
    Has anyone tried "UnityObject is null" vs ReferenceEquals(UnityObject, null) ?
     
    JohnTube likes this.
  7. shaderbytes

    shaderbytes

    Joined:
    Nov 11, 2010
    Posts:
    900
    Just remember null is not even really null from c# to c++ in unity. They might return null for your benefit , but in reality the actual object has perhaps not yet been destroyed from memory on the c++ end yet. Not that this info affects your speed tests , but good to know regardless. Always factor in the aspect of the managed layer we work upon in unity.

    Ive been reminded of this through many other mistakes at times. Like not removing a delegate , then destroying the scene that object was a part of , loading another scene , and then using the same delegate. you will find the object from the previous scene is stuck in memory now still receiving the delegate with errors , and you have no way to access it by then ;) so it has to have the delegate removed before trashing the scene.
     
  8. RMGK

    RMGK

    Joined:
    Sep 30, 2011
    Posts:
    75
    Heads up, "is null" is on par with ReferenceEquals. Might be easier to read. Just tested in the editor, about to do Android IL2CPP tests.
    .....
    Confirmed. About 50x difference. Tested on Samsung Galaxy S7

    Beware, ReferenceEquals and is null will only work if you are explicitly setting the Unity Object to null. It will give you undesirable values if an editor field is not populated. To avoid this set your exposed values to a default.

    public Object myObject = null;
     
    Last edited: Jun 4, 2019
    guycalledfrank and radiantboy like this.
  9. shaderbytes

    shaderbytes

    Joined:
    Nov 11, 2010
    Posts:
    900
    If you declare a unassigned variable / field in C# using visual studio - Visual studio will actually give you a compile time error in this regard .. "unassigned variable .. blah blah"
     
  10. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    Hey guys. Just been doing some testing of this and wanted to share. Looks like ReferenceEquals is faster than null for sure, and about the same as IsNull(). However IsNull() turned out to be a lot slower when testing in editor. Here's the link to the WebGL app if you want a try for yourself: http://cobaltplay.com/unityspeedtest/


     
  11. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    yeah, in my post above; a comparison between the two.
     
    RMGK likes this.
  12. radiantboy

    radiantboy

    Joined:
    Nov 21, 2012
    Posts:
    1,593
    Wow, just found this out when I had the same issue, very weird. I have removed many null checks which were mostly just for printing errors when things go wrong, but I can live without them. Do it also make sense do make a method like this, is it still faster?

    Code (CSharp):
    1.  private void isNull(GameObject go)
    2.         {
    3.             if (System.Object.ReferenceEquals(go, null))
    4.             {
    5.                 return true;
    6.             }
    7.             return false;
    8.         }
     
  13. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,797
    Is this an actual bottleneck in your game?! Have you used the profiler and proven it as such? If not you are wasting your time with speculative optimization.

    For the record I've been using Unity professionally now for eight years and never seen the need for this sorta stuff. Your mileage (of course) may vary, but worrying about this stuff seems like a holdover from the college intro to CS 101 days of "OPTIMIZE ALL THE THINGS!" type behavior.
     
  14. radiantboy

    radiantboy

    Joined:
    Nov 21, 2012
    Posts:
    1,593
    Haha I know exactly what you mean, though Ive been coding games for 25 years (even worked at rockstar for a while :) I did indeed fall into that trap still. The profiler said equality was really dragging things down, I culled the low hanging fruit, but the devil that is over optimisation got to me, I made an entire "slow" method fast but uncomprehensible (ended up having to make isNull() for various types and it all got rather silly). So I reverted it and only removed the worst offenders, it was worth it though, in some cases I was looping through 100 items in an array everytime the method was called!.. I assumed null was instant lol. Anyway as soon as I fixed that and some other stuff that method basically dropped off the profilers radar, so it is definitely a thing worth considering if you have nasty loops like mine were :)
     
    DavidNLN and OmarVector like this.