【 오브젝트 풀링이란? 】
오브젝트 풀링은 필요한 오브젝트를 매번 새로 생성하고 삭제하는 대신, 미리 생성해둔 오브젝트들을 재사용하는 기법입니다. 특히 총알, 적, 파티클 등 다량의 오브젝트가 빈번하게 생성되고 소멸되는 경우 큰 효과를 볼 수 있습니다. 이 방법은 객체 생성과 삭제 시 발생하는 메모리 할당과 해제 비용을 줄이고, 성능을 개선하는 데 도움이 됩니다.
하지만 언제나 오브젝트 풀링이 필요한 것은 아닙니다. 오브젝트 풀링을 무조건 적용하면 오히려 코드가 복잡해지고 관리가 어려워질 수 있습니다. 따라서 오브젝트 풀링을 적용할 때는 해당 상황에 대한 비판적 사고가 필요합니다. 과연 지금 이 상황에서 오브젝트 풀링이 성능 최적화에 기여할 만큼 중요한가? 라는 질문을 던져봐야 합니다.
【 Unity에서 오브젝트 풀링 구현 방법 】
Step 1: 기본적인 Pool 클래스 생성
오브젝트 풀링을 구현하려면 먼저 Pool 클래스를 만들어야 합니다. 이 클래스는 미리 생성된 오브젝트들을 저장하는 리스트와 오브젝트를 가져오거나 반환하는 메서드를 포함합니다.
public class ObjectPool : MonoBehaviour
{
[SerializeField] private GameObject prefab;
private List<GameObject> pool = new List<GameObject>();
public GameObject GetObject()
{
foreach (GameObject obj in pool)
{
if (!obj.activeInHierarchy)
{
obj.SetActive(true);
return obj;
}
}
GameObject newObj = Instantiate(prefab);
pool.Add(newObj);
return newObj;
}
public void ReturnObject(GameObject obj)
{
obj.SetActive(false);
}
}
Step 2: 미리 오브젝트 생성하기
게임의 시작 부분에 미리 오브젝트를 생성하여 풀에 추가해두면 첫 프레임에서 발생할 수 있는 성능 저하를 방지할 수 있습니다.
void Start()
{
for (int i = 0; i < 10; i++)
{
GameObject obj = Instantiate(prefab);
obj.SetActive(false);
pool.Add(obj);
}
}
Step 3: 게임 플레이 중 사용 및 반환
게임 플레이 중 필요할 때 GetObject를 호출해 오브젝트를 활성화하고, 사용 후에는 ReturnObject를 통해 비활성화하여 풀로 반환합니다.
【 오브젝트 풀링 vs Instantiate/Destroy 성능 비교 】
성능 테스트를 위해 두 가지 방식의 코드를 작성했습니다.
- 오브젝트 풀링 방식: 미리 생성해 둔 오브젝트를 Queue에 저장했다가 필요할 때 꺼내 사용하고, 필요하지 않을 때 다시 Queue에 반환하여 관리합니다.
- Instantiate/Destroy 방식: 매번 Instantiate로 오브젝트를 생성하고 Destroy로 파괴하는 방식입니다.
각 테스트는 5,000개의 오브젝트를 10번 반복해서 생성 및 비활성화하거나 파괴하며, 각 방식의 실행 시간을 측정했습니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPoolingMemoryTest : MonoBehaviour
{
// 생성할 오브젝트 프리팹
public GameObject objectPrefab;
// 풀에 미리 생성할 오브젝트 수
public int objectCount = 5000;
// 반복 테스트 횟수
public int repeatCount = 10;
// 오브젝트 풀을 저장할 큐
private Queue<GameObject> objectPool = new Queue<GameObject>();
void Start()
{
// 초기 오브젝트 풀 생성: 지정된 개수만큼 미리 오브젝트를 생성하여 비활성화 상태로 풀에 저장
for (int i = 0; i < objectCount; i++)
{
GameObject obj = Instantiate(objectPrefab); // 프리팹을 기반으로 오브젝트 생성
obj.SetActive(false); // 생성된 오브젝트를 비활성화
objectPool.Enqueue(obj); // 풀에 추가
}
}
void Update()
{
// 숫자 '1' 키를 눌렀을 때 풀링 방식으로 오브젝트 생성 및 재사용 테스트
if (Input.GetKeyDown(KeyCode.Alpha1))
{
StartCoroutine(SpawnWithPooling());
}
// 숫자 '2' 키를 눌렀을 때 Instantiate/Destroy 방식으로 오브젝트 생성 및 삭제 테스트
if (Input.GetKeyDown(KeyCode.Alpha2))
{
StartCoroutine(SpawnWithInstantiateDestroy());
}
}
// 오브젝트 풀링을 사용하여 오브젝트를 스폰하고 성능을 측정하는 코루틴
IEnumerator SpawnWithPooling()
{
float startTime = Time.realtimeSinceStartup; // 시작 시간 기록
// 지정된 횟수만큼 반복 실행
for (int i = 0; i < repeatCount; i++)
{
List<GameObject> activeObjects = new List<GameObject>(); // 활성화된 오브젝트 리스트 생성
// 지정된 수만큼 오브젝트를 활성화
for (int j = 0; j < objectCount; j++)
{
if (objectPool.Count > 0) // 풀에 오브젝트가 있을 때만 실행
{
GameObject obj = objectPool.Dequeue(); // 풀에서 오브젝트 가져오기
obj.SetActive(true); // 오브젝트 활성화
obj.transform.position = Random.insideUnitSphere * 5f; // 임의의 위치에 배치
activeObjects.Add(obj); // 활성화된 오브젝트 리스트에 추가
}
}
yield return new WaitForSeconds(0.1f); // 0.1초 대기 (오브젝트가 활성화된 상태 유지)
// 활성화된 오브젝트를 비활성화하고 풀에 다시 추가
foreach (var obj in activeObjects)
{
obj.SetActive(false); // 오브젝트 비활성화
objectPool.Enqueue(obj); // 풀에 반환
}
yield return new WaitForSeconds(0.1f); // 0.1초 대기
}
float endTime = Time.realtimeSinceStartup; // 종료 시간 기록
Debug.Log("Object Pooling Time: " + (endTime - startTime) + " seconds"); // 실행 시간 출력
}
// Instantiate와 Destroy를 사용하여 오브젝트를 생성 및 삭제하고 성능을 측정하는 코루틴
IEnumerator SpawnWithInstantiateDestroy()
{
float startTime = Time.realtimeSinceStartup; // 시작 시간 기록
// 지정된 횟수만큼 반복 실행
for (int i = 0; i < repeatCount; i++)
{
List<GameObject> activeObjects = new List<GameObject>(); // 활성화된 오브젝트 리스트 생성
// 지정된 수만큼 오브젝트를 새로 생성
for (int j = 0; j < objectCount; j++)
{
GameObject obj = Instantiate(objectPrefab); // 새로운 오브젝트 생성
obj.transform.position = Random.insideUnitSphere * 5f; // 임의의 위치에 배치
activeObjects.Add(obj); // 활성화된 오브젝트 리스트에 추가
}
yield return new WaitForSeconds(0.1f); // 0.1초 대기 (오브젝트가 활성화된 상태 유지)
// 생성된 오브젝트를 삭제
foreach (var obj in activeObjects)
{
Destroy(obj); // 오브젝트 삭제
}
yield return new WaitForSeconds(0.1f); // 0.1초 대기
}
float endTime = Time.realtimeSinceStartup; // 종료 시간 기록
Debug.Log("Instantiate/Destroy Time: " + (endTime - startTime) + " seconds"); // 실행 시간 출력
}
}
오브젝트 풀링 결과 영상
Instantiate/Destroy 결과 영상
【 오브젝트 풀링의 장단점 】
오브젝트 풀링은 분명한 장점이 있지만, 단점도 존재합니다. 이를 명확히 이해하고 필요에 따라 유연하게 활용하는 것이 중요합니다.
장점
- 성능 향상: 오브젝트의 빈번한 생성 및 삭제를 피할 수 있어 메모리 할당/해제와 관련된 비용을 절감합니다.
- GC(Garbage Collection) 부담 감소: 오브젝트를 재사용하므로 메모리 압박이 줄어들고, GC로 인한 성능 저하를 방지할 수 있습니다.
단점 및 주의사항
- 초기 메모리 비용: 오브젝트 풀을 미리 구성하려면 초기 메모리 자원을 더 많이 요구할 수 있으며, 제한된 메모리 환경에서 부담이 될 수 있습니다.
- 복잡한 관리 필요성: 풀에서 오브젝트 상태를 계속 추적하고 관리해야 하며, 코드가 복잡해질 수 있습니다.
- 과도한 풀 크기: 필요한 양 이상으로 오브젝트를 미리 생성하면 오히려 메모리 낭비로 이어질 수 있습니다.
【 오브젝트 풀링의 한계와 대안 기술 】
오브젝트 풀링이 모든 상황에서 최고의 성능을 보장하는 것은 아닙니다. 아래와 같은 경우에는 오브젝트 풀링이 효과적이지 않거나, 오히려 성능을 저하할 수 있습니다.
- 일회성 생성 오브젝트: 일회성 오브젝트는 굳이 풀링할 필요가 없으며, 불필요한 메모리 사용을 초래할 수 있습니다.
- 복잡한 오브젝트 풀링 구조: 지나치게 복잡한 구조는 코드 유지보수에 부담을 줄 수 있습니다.
대안: Unity의 Addressables 시스템
최근 Unity에서는 Addressables를 통해 리소스 관리를 보다 유연하게 할 수 있는 시스템을 제공하고 있습니다. Addressables는 런타임에서 리소스를 관리하며, 메모리와 퍼포먼스를 효율적으로 관리할 수 있도록 도와줍니다. Addressables을 활용하면, 초기 메모리 로드가 부담스러운 경우에 대안으로 고려할 수 있습니다.
【 오브젝트 풀링 성능 최적화 팁 】
Unity에서 오브젝트 풀링을 최적화하려면 아래의 팁들을 고려해 보세요.
- 메모리 Pool 크기 최적화: 풀의 크기는 필요한 만큼만 유지하는 것이 좋습니다. 필요 시 동적으로 확장할 수 있도록 코드를 작성합니다.
- Multi-threading 활용: Unity의 Job System이나 Task와 같은 비동기적 프로그래밍을 통해 풀에 오브젝트를 생성하거나 삭제하는 부분을 백그라운드에서 처리할 수 있습니다.
- 메모리 Alignment 고려: Android나 iOS처럼 제한된 메모리 환경에서는 메모리 효율을 높이기 위해 오브젝트 할당 위치와 크기를 신경 쓰는 것이 중요합니다.
【 결론: 오브젝트 풀링의 적절한 활용이 핵심 】
오브젝트 풀링은 잘 활용하면 성능을 향상할 수 있지만, 무분별하게 적용하면 오히려 프로젝트의 복잡도와 메모리 사용량을 증가시킬 수 있습니다. 필요에 따라 Addressables와 같은 대안 기술을 검토하고, 풀의 크기나 관리 방식을 최적화하는 것이 중요합니다. 또한 성능 향상이 반드시 필요하지 않은 경우에는 오브젝트 풀링을 피하는 것도 좋은 선택이 될 수 있습니다.
'개발💻 > Unity' 카테고리의 다른 글
[Unity] New input system 3 ( Behavior 설정 with code ) (0) | 2024.05.18 |
---|---|
[Unity] New input system 2 ( New input system 환경 설정 ) (0) | 2024.05.18 |
[Unity] New input system 1 ( 개념 정리 ) (0) | 2024.04.21 |
[Unity] 말풍선 UGUI (0) | 2022.11.07 |
[Unity] 텍스트 끝에 이모지 생성 (0) | 2022.10.04 |