ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Unity] 유니티 fake null과 관련한 이야기들
    쾌락없는 책임 (공부)/Unity 2023. 1. 16. 11:56
    반응형

    개요

    이전부터 문서들을 참고할 때 많이 보이는 내용이지만 이제 와서야 정리를 하게 된 내용입니다. 유니티가 오브젝트를 어떻게 가지고 있는지, GC, operator == 등과 관련한 이야기들을 확인할 수 있습니다.

     

     

    Destroy()를 하면 어떤 일이 벌어지는가

    일단 현상을 먼저 보고 설명을 드리겠습니다.

    IEnumerator Start()
    {
        CapsuleCollider2D col = gameObject.AddComponent<CapsuleCollider2D>();
        yield return null;
    
        Destroy(col);
        yield return null;
    
        IsUnityNull(col);
        IsSystemNull(col);
    }
    
    private void IsUnityNull(Object obj)
    {
        if (obj == null)
            Debug.Log("Unity is null");
        else
            Debug.Log("Unity is not null");
    }
    
    private void IsSystemNull(object obj)
    {
        if (obj == null)
            Debug.Log("System is null");
        else
            Debug.Log("System is not null");
    }

    시작하자마자 컴포넌트를 하나 붙인 뒤 다음 프레임에서 바로 Destroy()를 해 줍니다. 그런 다음 이게 null인지 알아보기 위해서 두 함수에 값을 넣어본 결과 UnityEngine.Object는 null이라고 하고 System.object는 null 이 아니라고 하는 기이한 현상이 일어나게 됩니다.

    자세한 구현 사항은 알 수 없으나 UnityEngine.Object의 경우 operator ==, != 들이 오버로딩 되어 있습니다. 이 때문에 == null 체크를 하는 게 System.object과는 다른 로직을 하게 됩니다. 위 코드를 보면 아래와 같은 로직을 따른 것이죠.

    • 유니티는 내부적으로 C++ 코드가 있어 실제 객체는 C++로, 클래스 내에서 보이는건 C#으로 래핑 되어 있음
    • Destroy()를 하면 C++의 실제 객체는 삭제
    • C#으로 래핑된 부분은 아직 GC의 동작을 기다리고 있음
    • UnityEngine.Object == 는 C# 래핑 부분뿐 아니라 실제 객체도 존재하는지에 대한 부분도 보고 있음
      • 위 경우 실제 C++ 객체는 Destroy에 의해 삭제되었으니 이를 알고 null이라 해줌
    • System.object의 경우 아직 C#의 래핑된 부분이 남아 있으므로 null이 아니라고 함

    때문에 이로 인해서 == null 체크의 시간이 오래 걸리게 되는 것입니다. 이 부분으로 인해서 고민할 부분이 생기게 되는 것이죠.

     

     

    추가적인 fake null이 있을까?

    일단 알아본 바로는 Destroy 된 것들 외 아직 할당이 안된 인스펙터 변수들이 포함되었습니다.

    public GameObject PuObject;
    [SerializeField]
    private GameObject SPObject;
    private GameObject PrObject;
    IEnumerator Start()
    {
        yield return null;
        Debug.Log($"public GameObject is null : {PuObject == null}, {(object)PuObject == null}");
        Debug.Log($"serialized private GameObject is null : {SPObject == null}, {(object)SPObject == null}");
        Debug.Log($"private GameObject is null : {PrObject == null}, {(object)PrObject == null}");
    }

    이렇게 인스펙터에 할당이 안된 변수들은 operator == 같은 연산을 해야 원하는 값을 얻을 수 있을 겁니다.

     

     

    operator ==처럼 fake null을 검출하는 연산이 있는가?

    operator == 이 어떤 과정을 거치는지, 왜 느린지를 알아봤으니 이를 개선해봐야 할 것 같습니다. 먼저 여러 연산들을 보면서 operator ==과 동일한 값을 내는 연산이 있는지 알아보겠습니다.

    private void DefaultCompare(Object obj)
    {
        Debug.Log($"== : {(obj == null)}");
    }
    
    private void SystemIsCompare(object obj)
    {
        Debug.Log($"System is : {(obj is null)}");
    }
    
    private void UnityIsCompare(Object obj)
    {
        Debug.Log($"System is : {(obj is null)}");
    }
    
    private void ReferenceCompare(object obj)
    {
        Debug.Log($"ReferenceEquals : {ReferenceEquals(obj, null)}");
    }
    
    private void ObjectReferenceCompare(object obj)
    {
        Debug.Log($"Object.ReferenceEquals : {Object.ReferenceEquals(obj, null)}");
    }
    
    private void JustCompare(Object obj)
    {
        if(obj)
            Debug.Log($"just Not null");
        else
            Debug.Log($"just null");
    }

    위 코드를 통해 fake null 상태인 [SerializeField] private Gameobject 변수를 넣고 본 결과입니다. 일단 보시는 대로 ==와 바로 if 문의 조건에 넣은 걸 제외하고는 다 fake null인지 모르는 상태입니다. 즉, 유니티에서 준비한 operator ==과 암시적 bool 변환을 제외하고 fake null을 검증할 수 없습니다.

     

     

    [C#] is null vs == null

    개요 아는 지인이 한번 질문을 했었습니다. 'is null이 == null보다 빠르다는데 이거 원리를 아냐?'. 완전 처음 듣는 이야기라 바로 검색을 해 봤는데 또 처음 보는 내용들이 올라와서 답장을 할 수

    husk321.tistory.com

    즉 이전에 'is' 연산이 유니티에서 빨랐던 이유는 is 연산의 경우 fake null을 알지 못하고 C#의 조건만 비교하기 때문입니다. 그래서 유니티에서 null 체크를 할 때는 가급적 operator ==과 암시적 bool 변환을 사용하는 게 좋습니다.

     

     

    그럼 operator == 말고 사용할 수 있는 경우는?

    만일 그렇다면 속도에 있어서 아쉬운 부분이 많을 것입니다. 그러면 operator == 대신 다른 연산을 사용할 수 있는 경우는 어떤 경우일까요?

    • GameObject의 transform 같은 '당연히 있어야 하는 객체'들을 캐싱하는 경우
    • UnityEngine.Object와 같이 fake null일 염려가 아닌 경우
    • 싱글톤 객체와 같이 파괴가 예정되어 있지 않은 객체
    • Destroy를 사용하지 않는 경우
      • 이 경우 프로그래머에게 '명시되지 않은 규약'이 생기는 거라 비추천
      • 규약을 더할 바에는 차라리 operator ==를 사용하는 게 좋을 것 같습니다.

    뭐 이 정도가 될 것 같은데 더 추가되는 대로 포스트를 수정하겠습니다.

     

     

    참고 자료

     

    Custom == operator, should we keep it? | Unity Blog

    When you do this in Unity: if (myGameObject == null) {} Unity does something special with the == operator. Instead of what most people would expect, we have a special implementation of the == operator. This serves two purposes: 1) When a MonoBehaviour has

    blog.unity.com

    대부분의 내용을 가장 공신력 있는 유니티 공식 블로그를 참고했습니다. 왜 성능이 떨어지는 operator ==를 고수하는가에 대한 이야기로 한 번쯤 읽어볼 만한 것 같습니다.

    반응형

    댓글

Designed by Tistory.