Classes deriving from Unity. Object inherit equality operators that change the behaviour of the == and != operators. While these operators will perform standard . NET reference equality, if comparing one side to null, these operators will call native code to check if the underlying native engine object is still alive.
这段解释来自Rider的提示:https://github.com/JetBrains/resharper-unity/wiki/Avoid-null-comparisons-against-UnityEngine.Object-subclasses
UnityEngine 底层是C++写的,所以说,其实我们对UnityObject的空引用检查都会再深入到里面C++那一层去再检查,这个检查是比较消耗的。 之前曾经遇到因为使用了 ?. 语法糖而导致的 this 指针为null的奇妙bug,大概也是因为这个原因。
现在的公司是做手游的,大家对性能都比较看重,不过我也真的好奇这个检查到底有多消耗,决定跑一跑试试。
Test Case 因为是做手游,随意干脆就在手机上测试好了。 大概是这样的环境: Unity 2019.4 LTS
移动设备这边是 Google Pixel Verzion(俺穷买美版) Android 9 (已获取Root权限)
TestCase 这边其实我都是瞎弄的。
分别进行三种测试: Unity Object 的空引用检查; POCO Object 的空引用检查; bool 的直接检查;(因为我觉得这是一个代替Unity Object空引用检查的一个方法
每一种检查都有检查到true和false的两种情况,并循环跑多次。
然后,上代码!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 namespace TempCheckTest { public class TempObjA { private int m_tempNum = 56 ; } public class NullCheckTester : MonoBehaviour { private Transform m_emptyTransform = null ; private Transform m_selfTransform = null ; private TempObjA m_emptyObjA = null ; private TempObjA m_objA = null ; private bool m_boolTrue = true ; private bool m_boolFalse = true ; [Header("test" ) ] public int m_checkTimes = 100000 ; [Header("UI side" ) ] public UnityEngine.UI.Text m_outPutText = null ; public UnityEngine.UI.Button m_doUnityObjTestButton = null ; public UnityEngine.UI.Button m_doPocoObjTestButton = null ; public UnityEngine.UI.Button m_doBoolTestButton = null ; public UnityEngine.UI.InputField m_inputField = null ; [Button("init awake" ) ] public void InitTester ( ) { m_emptyTransform = null ; m_selfTransform = this .transform; m_emptyObjA = null ; m_objA = new TempObjA(); m_boolTrue = true ; m_boolFalse = false ; m_outPutText.text = string .Empty; m_doUnityObjTestButton.onClick.RemoveAllListeners(); m_doPocoObjTestButton.onClick.RemoveAllListeners(); m_doBoolTestButton.onClick.RemoveAllListeners(); m_doUnityObjTestButton.onClick.AddListener(DoUnityObjNullCheckTest); m_doPocoObjTestButton.onClick.AddListener(DoPOCOObjNullCheckTest); m_doBoolTestButton.onClick.AddListener(DoBoolObjNullCheckTest); m_inputField.onValueChanged.RemoveAllListeners(); m_inputField.onValueChanged.AddListener(OnInputNumberChanged); m_checkTimes = 100000 ; m_inputField.text = m_checkTimes.ToString(); } [Button("do unity.object null check test" ) ] public void DoUnityObjNullCheckTest ( ) { System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch(); watcher.Start(); for (int i = 0 ; i < m_checkTimes; i++) { if (null == m_emptyTransform) {} if (null == m_selfTransform) {} } watcher.Stop(); string outPut = $"do unity.object null check test 2*{m_checkTimes} cost {watcher.ElapsedMilliseconds} ms" ; m_outPutText.text = outPut; Debug.Log(outPut); } [Button("do poco object null check test" ) ] public void DoPOCOObjNullCheckTest ( ) { System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch(); watcher.Start(); for (int i = 0 ; i < m_checkTimes; i++) { if (null == m_emptyObjA) {} if (null == m_objA) {} } watcher.Stop(); string outPut = $"do poco object null check test 2*{m_checkTimes} cost {watcher.ElapsedMilliseconds} ms" ; m_outPutText.text = outPut; Debug.Log(outPut); } [Button("do bool check test" ) ] public void DoBoolObjNullCheckTest ( ) { System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch(); watcher.Start(); for (int i = 0 ; i < m_checkTimes; i++) { if (m_boolFalse) {} if (m_boolTrue) {} } watcher.Stop(); string outPut = $"do bool check test 2*{m_checkTimes} cost {watcher.ElapsedMilliseconds} ms" ; m_outPutText.text = outPut; Debug.Log(outPut); } public void OnInputNumberChanged (string arg0 ) { int result = 0 ; if (!Int32.TryParse(arg0, out result)) result = Int32.MaxValue; m_checkTimes = result; } #region mono method private void Reset ( ) { InitTester(); } private void Awake ( ) { InitTester(); } private void OnEnable ( ) { this .enabled = false ; } #endregion } }
Test Result
分别进行10w次和100w次测试看看结果。
10w次: Unity Object null check 4~7ms POCO Object null check 0ms bool check 0ms
100w次: Unity Object null check 45~60ms POCO Object null check 3~4ms bool check 2ms
当检测数量非常多的时候,Unity Object 空引用检查的性能消耗比其他两个大了很多很多。现在的项目里大部分东西其实都是POCO的。 似乎把UnityObject Destroy之后再手动把变量置空是个好习惯(