유니티 리스트 풀 Unity ListPool<T> 로 리스트 최적화

 

 

어느 프로젝트건 함수 내부에서 연산을 위해 함수 내부에서 리스트 컨테이너를 선언하고 조작하는 일은 빈번하다.

 

특히나 렌더링 쪽에서는 자주 사용되기 때문에

이미 내부적으로는 연산 컨테이너로서 리스트 풀을 사용하고 있다고 한다. 

 

때문에 크기가 큰 컨테이너를 지역변수로 자주 사용하는 경우 멤버 변수로 컨테이너를 선언해 놓고서 사용하라고 권장한다.

 

 

하지만 멤버변수가 특성상 상황에 따라 메모리에 계속 물고있게 되는 경우도 있으며 보통 멤버변수는 상단에 위치하기 때문에 가독성 측면에서도 좋지 않다.

 

그래서 정적 리스트를 오브젝트 풀링 하듯 풀링 하는 방식이 리스트 풀이다.

일부 Astar Pathfinding Project 같은 에셋들을 뜯어보면 자체적으로 리스트 풀을 만들기도 했는데 

이번에 유니티 2021부터 공식 지원하기 시작했다.

 

유니티 공식문서 : ListPool<T0>
https://docs.unity3d.com/ScriptReference/Pool.ListPool_1.html

 

 

사용법은 놀라울 정도로 간단한데

 

Get해서 사용하고 사용이 끝나면 Release하면 끝이다.

 

 

 

테스트해보기

리스트 풀이 정말로 작동한다면 GC Alloc이 낮게 측정되어야 한다.

이를 프로파일러 마커를 사용하여 체크해 보자.

 

리스트 길이 1만 개를 1만 번 반복해 봤다.

using System.Collections.Generic;
using Unity.Profiling;
using UnityEngine;

public class ListPoolTest : MonoBehaviour
{
    const int repeatCount = 10000;
    const int listLength = 10000;

    static readonly ProfilerMarker s_Marker_notUse = new("ListPool_NotUse");
    static readonly ProfilerMarker s_Marker = new("ListPool_Use");

    void Start()
    {
        using (s_Marker_notUse.Auto())
        {
            for (int repeat = 0; repeat < repeatCount; repeat++)
            {
                var list = new List<int>(listLength);

                for (int i = 0; i < listLength; i++)
                    list.Add(i);

                int result = 0;

                for (int i = 0; i < listLength; i++)
                    result += list[i];

                UnityEngine.Debug.Log($"일반리스트 사용 : {result}");
            }
        }

        using (s_Marker.Auto())
        {
            for (int repeat = 0; repeat < repeatCount; repeat++)
            {
                var list = UnityEngine.Pool.ListPool<int>.Get();

                for (int i = 0; i < listLength; i++)
                    list.Add(i);

                int result = 0;

                for (int i = 0; i < listLength; i++)
                    result += list[i];

                UnityEngine.Debug.Log($"리스트풀 사용 : {result}");
                UnityEngine.Pool.ListPool<int>.Release(list);
            }
        }
    }
}

 

 

결과

일반 리스트 new 사용 : 387 MB GCAlloc 

리스트 풀 사용 : 7.6MB GCAlloc

리스트 풀 쪽이 확연하게 GC가 줄어들었다.

 

 

마치며..

List 풀 외에도 다른 타입들도 존재한다.

마지막 UnsafeGenericPool는 컬렉션 검사를 수행하지 않는다고 함.

 

 

주의점

상황에 따라서는 Get()해 온 리스트를 리턴 시켜서 다른 함수에 전달해서 사용하다가

나중에 Release 해야 되는 경우도 존재한다.

하지만 그 경우 Release를 실수로 까먹으면 메모리가 줄줄 새니 조심하도록 하자.

 

또한 여느 풀링이 그렇듯 스레드 세이프하지 않다!

 

 

댓글

Designed by JB FACTORY