• 티스토리 홈
  • 프로필사진
    SiJun-Park
  • 방명록
  • 공지사항
  • 태그
  • 블로그 관리
  • 글 작성
SiJun-Park
  • 프로필사진
    SiJun-Park
    • 분류 전체보기 (190)
      • Unity (148)
        • 출시 해보기 (Slime Company) (3)
        • 뱀서류 Project (34)
        • Defense Project (20)
        • FPS Project (30)
        • RPG Project (39)
        • 기타 - 개발 (22)
      • 개발 (35)
        • 임베디드 소프트웨어 (7)
        • 컴파일러 (6)
        • 기계학습 (8)
        • 보안 (8)
        • 그래픽스 (2)
        • 그 외 (4)
      • 코딩문제 (6)
  • 방문자 수
    • 전체:
    • 오늘:
    • 어제:
  • 최근 댓글
      등록된 댓글이 없습니다.
    • 최근 공지
        등록된 공지가 없습니다.
      # Home
      # 공지사항
      #
      # 태그
      # 검색결과
      # 방명록
      • Object Pooling 회고
        2026년 01월 06일
        • SiJun-Park
        • 작성자
        • 2026.01.06.:14

        제가 처음부터 이렇게 하면 좀 최적화 되지 않을까? 하고 생각을 하고 만들어 보니 오브젝트 풀링이였고, 여러가지 문제점 들을 발견해

         

        배우고 하나 하나 고쳐나가며 만들어 나갔던 기능인데 문제점이 있을 수 있다고 합니다.

         

        생각치도 못했는데 오로지 장점만 보여서 그냥 달려왔지만, 그렇지 않다는 것을 알게되고 놀랐습니다.

         

        즉 "항상 좋지는 않다!"입니다.

         

        어느 부분에서 문제가 생기나?

        메모리를 미리 점유한다는 것이 문제가 있습니다.

         

        예로 들어서 100개의 풀을 만들어 두었다고 하고, 10개만 계속 활성/비활성을 통해 사용을 했다고 한다면 

        90개가 따로 놀게 됩니다. 

         

        즉 초기화 비용을 지불을 하고 메모리를 미리 점유 했지만 사용하지 않는 부분입니다.

         

        그것이 왜 단점?

        저도 미리 다 생성을 해두고 활성/비활성화 하는 것 보다 

        오브젝트 풀링을 통해 사용을 할 때 생성하고 비활성화하는 것이 아무리 봐도 좋지 않나? 생각을 했습니다.

         

        하지만 예로 들어서

         

        총알 1000발

        이펙트 500개

        몬스터 200마리

         

        이렇게 풀링을 한다면, 전부 생성을 미리 하지만 대부분 실제로는 사용이 되지 않습니다.

         

        생성을 미리하기 때문에 씬 진입 시 프레임 드랍이 일어나고 메모리 피크가 상승하게 됩니다.

         

        즉 리소스 낭비가 됩니다.

         

        그러면 풀링은 나쁜건가?

        그렇진 않습니다.

         

        Instantiate / Destroy는 CPU 비용과 GC 폭탄입니다. 

        그래서 총알, 이펙트, 데미지 텍스트 같은 짧은 생명 오브젝트에는 거의 필수적입니다.

         

        그래서 풀링의 위의 문제들이 있지만 이것은 어떻게 설계했느냐에 따라 해소가 될 수 있습니다.

         

        List<GameObject> pool = new List<GameObject>();
        
        foreach (var obj in pool)
        {
            if (!obj.activeSelf)
            {
                return obj;
            }
        }

         

        예로 들어 위와 같이 구성을 했다면 매 요청마다 foreach문을 돌게 되고 Enumerator를 할당하게 됩니다.

        *이전에 포스팅 하였던 람다 / LINQ 쓰면 추가 할당이 됩니다.

         

        결과 GC 발생이 많이 되게 됩니다.

         

        그래서 람다 + 풀링 = GC 폭탄이 되어버립니다.

         

        Stack<GameObject> pool;
        
        var obj = pool.Pop();
        pool.Push(obj);

        위와 같이 Stack / Queue를 사용하면 할당이 없어지고, 탐색이 없어지며 GC가 없어지게 됩니다.

         

        또 숨겨진 GC의 원인이 하나 있습니다.

        그건 바로 풀 반환 시 new WaitForSeconds(), 코루틴 남발, OnEnable에서 이벤트 재등록, 리스트 Clear 후 Add 반복 

        즉 Destory를 안했는데 GC가 터지게 된다면 위와 같은 이유가 대부분입니다.

        어떻게 고치나?

        1. 가변 풀

        이건 제가 가장 많이 사용하는 방법인 고정 풀 방법을 해결하는 방법입니다.

        처음에 생성 할 만큼 미리 풀을 지정하지 말고 초기에는 사용하는 만큼만 생성하고

        부족하면 추가 생성하는 방식입니다.

         

        2. Lazy Initialization(지연 생성)

        이 방법은 처음에는 아예 만들지 않습니다.

        다만, 실제로 요청될 때 생성이 되고 풀에 반환을 하는 형식입니다.

         

        3. 안 쓰는 풀 정리

        일정 시간 이상 미사용을 한다면 일부를 반환합니다.

        그래서 씬을 전환 할 때 풀 리셋을 해줍니다.

         

        예로들어 웨이브 기반 게임이면 웨이브 끝날 때 축소를 합니다.

         

         

        네트워크 게임에서 오브젝트 풀링 주의점

        이것은 제가 공부하다가 알게 되었는데, 멀티플레이에서는 풀링을 잘못 쓰면 버그가 터져난다고 합니다.

         

        예로 들어서 PhotonView / NetworkIdentity를 포함한 오브젝트를 Disable과 Enable을 반복한다면

        ViewID가 꼬이게 되고 RPC가 날라가지 않는 버그가 발생하고 다른 클라이언트에서 오브젝트가 유령처럼 남게 됩니다.

         

        그래서 네트워크 동기화 대상 오브젝트는 기본 풀링 금지라고 합니다.

         

        대신 로컬 표현만 풀링을 합니다.

         

        예로 들어서 총알 시각 효과, 히트 이펙트, 데미지 텍스트 이것들은 풀링을 하나 실제 판정 오브젝트 같은 것들은 풀링을 하지 않는 것이 좋습니다.

         

         

        그래서 무엇을 고쳐봤나?

            public void CreatePool(string key, GameObject prefab, Transform parent, int defaultCount = 10, int maxCount = 100)
            {
                if (_pools.ContainsKey(key)) return;
        
        
                _prefabs[key] = prefab;
                _parents[key] = parent;
        
                _pools[key] = new ObjectPool<GameObject>(
        
                    createFunc: () => Instantiate(prefab, parent),
                    actionOnGet: obj => obj.SetActive(true),
                    actionOnRelease: obj => obj.SetActive(false),
                    actionOnDestroy: obj => Destroy(obj),
                    collectionCheck: false,
                    defaultCapacity: defaultCount,
                    maxSize: maxCount
        
                );
                for (int i = 0; i < defaultCount; i++) _pools[key].Release(_pools[key].Get());
            }

         

        위와 같은 코드에서

        actionOnGet: obj => obj.SetActive(true),
        actionOnRelease: obj => obj.SetActive(false),

        이 부분을 고쳐줬습니다. 

         

        왜냐하면 HP, 타이머 등 초기화가 되지 않은 상태이기 때문입니다.

        public interface IPoolable
        {
            void OnSpawn();
            void OnDespawn();
        }

        그래서 위와같이 interface를 하나 선언을 해줍니다.

        actionOnGet: obj =>
        {
            obj.SetActive(true);
            obj.GetComponent<IPoolable>()?.OnSpawn();
        },
        actionOnRelease: obj =>
        {
            obj.GetComponent<IPoolable>()?.OnDespawn();
            obj.SetActive(false);
        }

        그리고 해당 함수를 불러와 주어 풀링 버그의 주요한 곳을 해결하였습니다.

         

        'Unity > 기타 - 개발' 카테고리의 다른 글

        Culling 공부  (0) 2026.01.11
        Addressable & 메모리 로딩 공부  (0) 2026.01.08
        람다식 / LINQ 회고  (0) 2026.01.06
        foreach / Animator 최적화 공부 기록  (0) 2026.01.01
        Transform 그리고 StopAllCoroutines 공부 기록  (1) 2026.01.01
        다음글
        다음 글이 없습니다.
        이전글
        이전 글이 없습니다.
        댓글
      조회된 결과가 없습니다.
      스킨 업데이트 안내
      현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
      ("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
      목차
      표시할 목차가 없습니다.
        • 안녕하세요
        • 감사해요
        • 잘있어요

        티스토리툴바