ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 오브젝트 풀링 [펌]
    Html Canvas 2022. 6. 22. 01:52

    출처: https://notyu.tistory.com/m/64

     

    1. 오브젝트 풀링(Object pooling)

     

    오브젝트 풀링은 개체를 할당하고 파괴하는 대신, 미리 오브젝트 집합(Object Pool)을 생성하고, 필요할 때에 불러와 사용하고, 사용한 개체를 다시 개체 집합에 반환하여, 개체들을 재사용하는 디자인 패턴 중의 하나이다.   

     

    오브젝트 풀링은 미리 생성된 개체를 재사용하므로 개체를 생성하고, 파괴하는데 드는 시간 및 비용의 낭비를 줄일 수 있다. 오브젝트의 생성 및 파괴는 자원의 낭비가 심하다. 게다가, 오브젝트의 파괴는 가비지 컬렉션(Garbage Collection )을 호출하게 된다. 가비지 컬렉션은 시스템에서 자동으로 호출되며, 수집 과정은 상당한 CPU 시간을 요구한다. 최적화와 관련된 글을 접하게 되면 자주 언급되는 것이 가비지 컬렉션이다. 메모리를 효율적으로 관리하지만, 

    이 역시 프로그램이 실행되고, 해제가 될 때까지 지속적으로 자원을 사용한다.

     

    한 번만 사용하는 오브젝트는 Destroy()를 호출하여 파괴를 하면 된다. 하지만 여러 번 반복적으로 사용하게 되는 오브젝트는 오브젝트 풀링을 사용하는 것이 최적화에 좋다. 

     

    유니티는 총알, 스킬 이펙트와 같은 반복적으로 사용하는 오브젝트는 오브젝트 풀링을 사용하여, 오브젝트를 재사용하여 메모리를 효율적으로 관리하도록 권장하고 있다(유니티 문서).

     

    오브젝트 풀링은 미리 오브젝트 집합을 생성하여, 메모리를 할당하기 때문에, 오브젝트를 파괴하는 것보다 메모리를 더 많이 사용하는 단점이 존재한다. 오브젝트 풀링은 오브젝트를 파괴하지 않고, 오브젝트를 보이지 않게 할 뿐 메모리에서 해제하는 것은 아니다.

     

    오브젝트 풀링 디자인 패턴은, 사용 가능한 오브젝트 집합, 사용 중인 오브젝트 집합, 두 개의 오브젝트 집합을 사용하여 구현한다. 오브젝트 풀링을 간단하게 요약하면 재사용이다. 본 글에서는 오브젝트 집합을 생성하고 이를 재사용하는 것에 초점을 맞추어 구현한다.

       

     

    2. 큐(Queue)

     

    오브젝트 풀링은 미리 생성하고 이를 반복적으로 사용하는 것이다. 따라서 오브젝트 재사용은 다음과 같다.

     

    호출 → 사용 → 반납  호출 →  사용 →  반납 →  … 반복  

     

    오브젝트 집합에서 호출하여 사용하고 이를 다시 오브젝트 집합에 반환한다. 오브젝트 집합에서 호출할 때, 이미 사용 중인 오브젝트를 호출하면 잘못된 접근이다. 

     

    현실에서 마트에 가서 물건을 사고 계산대에 가서 계산을 하고 나온다. 가끔 사지 않은 물품이 있을 경우 다시 마트에 들어가 계산을 하고 나온다. 계산대에서 한번 사고 다시 가서 기다리는 사람이 있을 때에는 줄을 서서 대기한다. 대기 줄에 서서 먼저 들어온 사람이 계산대에서 값을 지불하고 나간다. 영어 문화권에서는 이를 큐(Queue)라고 한다. 

    프로그램에서도 이를 로직으로 구현한 것이 자료구조 중의 하나인 큐(Queue)이다. 오브젝트 집합에서 사용한 오브젝트는 다시 오브젝트 집합으로 넘길 때, 호출 순서를 가장 마지막으로 하면 사용 중인 오브젝트를 다시 호출하는 일은 발생하지 않는다.

     

    queue

    사진 출처 : namu.wiki/w/%ED%81%90(%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0)

     

     

     

    3. 오브젝트 풀링 구현 

     

    아래의 예제는 큐를 사용하는 오브젝트 풀링이다.

     

    아래의 예제 코드는 단일 오브젝트에 대하여 재사용한다. 이를 확장하여 다양한 오브젝트를 풀에서 미리 생성하고 재사용하는 부분은 언급하지 않는다. 

     

    3.1 재사용 오브젝트(Poolable object)

    재사용할 게임 오브젝트는 활성화가 되면 Z 축 방향으로 움직이는 간단한 로직으로 구성된 게임 오브젝트이다. 활성화되면 움직이고, 활성화가 끝나면, 오브젝트 집합으로 돌아가 비활성화한다. 

    재사용 게임 오브젝트에 Poolable class를 추가하고, 프리팹화한다. 

     

     

    Poolable Object

     

     

    재사용 오브젝트는 Pool의 인스턴스를 가지고 있어, 일정 시간이 지나면, Pool로 반환한다.

    Init() 함수는, Pool의 인스턴스를 받는 함수이다. Pool에 의해 호출된다.

    CleanUp() 함수는 오브젝트의 상태를 초기화한다.   

    using UnityEngine;
    
    public class Poolable : MonoBehaviour
    {
        Pool pool;
        float activeTime;
        float activeTimeRate = 3.0f;
    
        // Update is called once per frame
        void Update()
        {
            activeTime -= Time.deltaTime;
            if (activeTime > 0)
            {
                transform.position += new Vector3(0, 0, 0.1f);
            }
            else
            {
                pool.Enqueue(gameObject);
            }
        }
        public void Init(Pool _pool)
        {
            pool = _pool;
        }
    
        public void CleanUp()
        {
            activeTime = activeTimeRate;
        }
    }

     

     3.2 오브젝트 집합(Pool)

     

    풀(Pool)은 재사용 오브젝트의 프리팹을 사용해, 시작 시 재사용 오브젝트의 인스턴스를 생성한다.  

     

    Object Pool

      

     

    Start() 함수에서 재사용할 오브젝트의 인스턴스들을 생성한다. 지정된 개수(objectpoolCount) 만큼 인스턴스를 생성한다. 생성된 인스턴스의 Poolable class의 Init() 함수를 호출하여, 자신의 인스턴스를 전달하고, 오브젝트를 비활성화한다.

     

    Dequeue() 함수는 풀에서 인스턴스를 받아와 오브젝트를 초기화하고, 활성화시킨다. 이후 오브젝트를 사용자에게 전달한다. 풀에 재사용 가능한 오브젝트가 없을 경우, 오브젝트의 인스턴스를 새로 생성하고, 이를 전달한다.

     

    Enqueue() 함수는 사용한 오브젝트를 비활성화시키고, 사용 가능한 상태로 만든다.

     

    using System.Collections.Generic;
    using UnityEngine;
    
    public class Pool : MonoBehaviour
    {
        public GameObject poolableObject;
        public int objectpoolCount = 10;
        Queue<GameObject> objectPool = new Queue<GameObject>();
    
        // Start is called before the first frame update
        void Start()
        {
            for (int i = 0; i < objectpoolCount; i++)
            {
                CreatePooledObject();
            }
        }
    
        void CreatePooledObject()
        {
            GameObject temp = Instantiate(poolableObject);
            temp.GetComponent<Poolable>().Init(this);
            temp.SetActive(false);
            objectPool.Enqueue(temp);
        } 
    
        // Get Object
        public GameObject Dequeue()
        {
            if (objectPool.Count <= 0)
            {
                CreatePooledObject();
            }
    
            GameObject dequeueObject = objectPool.Dequeue();
            dequeueObject.GetComponent<Poolable>().CleanUp();
            dequeueObject.SetActive(true);
            return dequeueObject;
        }
        // Back to pool
        public void Enqueue(GameObject _enqueueObject)
        {
            _enqueueObject.SetActive(false);
            objectPool.Enqueue(_enqueueObject);
        }
    }

     

    3.3 Player 예제

     

     플레이어는 Pool class의 인스턴스를 가지고 있는다.

     

    Player

    플레이어는 풀에서 Dequeue() 함수를 호출하여 게임 오브젝트를 받아온다. 이후 자신의 위치에 오브젝트를 위치하도록 한다.

    using UnityEngine;
    
    public class PlayerExample : MonoBehaviour
    {
        public Pool pool;
    
        // Update is called once per frame
        void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                GameObject pooledObject = pool.Dequeue();           
                pooledObject.transform.position = transform.position;
            }
        }
    }

     


    참조

    https://en.wikipedia.org/wiki/Object_pool_pattern

Designed by Tistory.