C# 메모리 관리 – 힙 파편화, 가비지 컬렉션, 오브젝트 풀링 Heap Fragmentation, Garbage Collection and Object Pooling

C# 메모리 관리 – 힙 파편화, 가비지 컬렉션, 오브젝트 풀링 Heap Fragmentation, Garbage Collection and Object Pooling

memory management


힙 파편화, 가비지 컬렉션, 오브젝트 풀링 Heap Fragmentation, Garbage Collection and Object Pooling

힙heap은 크기가 큰 메모리 공간으로서 데이터가 랜덤 위치에 저장됩니다.
사실, 완전히 랜덤은 아닙니다. 운영체제가 메모리를 들여다보고 요청한 데이터가 저장될 수 있는 크기의 공간 중 첫 번째 공간을 사용하게 됩니다. 힙의 위치와 크기는 여러분이 선택한 플레이어의 플랫폼에 따라 다릅니다.

예를 들어, integer를 요청한 경우, 운영체제는 4개의 연속적인 바이트 크기의 빈 메모리 공간을 찾습니다. 사실, 프로그램이 실행되면, 사용되는 메모리 공간이 사용된 뒤 해제되는 과정이 반복되면서 결국 힙 파편화 heap fragmentation이 발생할 수 있습니다.

heap fragmentation

사진은 힙 파편화 heap fragmentation의 예를 보여줍니다. 다소 과장된 면이 있지만, 개념을 이해하시기엔 충분합니다. 사진에서 data3를 할당하려는 것을 확인하실 수 있을겁니다. state1에는 data3가 있지만, state2에서 제거되었습니다. 이런식으로, 데이터가 할당되고 해제되면서 남은 공간이 생기게 되고 결국 힙 파편화가 발생하게 됩니다.

유니티는 .NET의 managed 메모리를 사용합니다. C 프로그램에서 힙 파편화 heap fragmentation는, 실제로 충분한 크기의 메모리 공간이 있어도, 필요한 크기의 연속적인 메모리 블록을 찾을 수 없기 때문에 메모리를 할당할 수 없는 상황이 발생할 수 있습니다 – managed 메모리는 이러한 제한 사항이 없습니다.

실제로 메모리 블록을 찾을 수 없는 경우, .NET 메모리 관리 및 가비지 컬렉션 시스템이 작동해서 사용 중인 메모리를 이동시켜서 메모리 파편화를 제거합니다. 유니티에서는 가비지 컬렉터의 구현 방식 때문에 이 동작이 발생하지 않습니다. 대신에, 한 번에 여러 오브젝트를 제거해서 필요한 공간을 확보하는 방식에 가깝습니다. 하지만, 이 방식은 필요한 크기를 찾을 때까지 메모리 블록을 이동하는 것만큼 효율적이지 않습니다.

이 경우에 프로그래머가 취할 수 있는 해결책 중 하나가 바로 오브젝트 풀링 Object pooling 입니다. state1에서 data3을 삭제하는 대신 나중에 사용을 위해서 단순히 비활성화 시키는 겁니다. 이렇게 하면 힙 파편화 heap fragmentation을 피할 수 있습니다.

데이터를 삭제하지 않고, 잠시 재워두었다가 필요할 때 깨웁니다.

오브젝트 풀링Object Pooling은 이점이 많습니다. 첫 째, 위에서 살펴본 경우를 피할 수 있습니다. 둘 째, 객체의 인스턴스를 생성하고 삭제하는 것을 피할 수 있습니다. gameObject.SetActive(true/false);를 사용하면 오브젝트를 재우고 깨우는 것이 가능합니다.

마지막으로, 객체를 삭제하지 않기 때문에, 비싼 동작인 가비지 컬렉터를 호출하지 않습니다.

using UnityEngine;
using System.Collections;
 
public class ObjectScript : MonoBehaviour
{
    public GameObject prefab;
    GameObject[] objectArray = new GameObject[5];  
    public static int counter; 
 
    private void Start()
    {
        for(int i = 0;i < objectArray.Length;i++)
        {
            objectArray[i] = (GameObject)Instantiate(prefab,new Vector3(0,0,0),Quaternion.identity);
            objectArray[i].SetActiveRecursively(false);
        }
    }

    void Createobject()
    {
        if(counter < objectArray.Length)
        {    
            // iterate through array
            for(int i = 0;i < objectArray.Length;i++)
            {  
                 // Find an inactive object                                     
                 if(!objectArray[i].active)
                 {    
                     // Increase the counter, activate the object, position it                                              
                     counter++;                                             
                     objectArray[i].SetActiveRecursively(true);            
                     objectArray[i].transform.position = new Vector3(0,0,0);  
                     return;
                 }
             }
             return;
        }
        else return;
    }
}
void OnCollisionEnter(Collision other)
{
    if(other.gameObject.tag == "Object")
    {
        other.gameobject.SetActiveRecursively(false);       //Deactivate the object
        ObjectScript.counter--; 			    //Decrease counter
    }
}

이 씬은 동시에 5개 이상의 오브젝트를 깨우지 않습니다. 한 오브젝트가 비활성화되면 나중에 언제든지 필요할 때 다시 활성화시킬 수 있습니다. 따라서 가비지 컬렉션이 필요없습니다.

여러분은 이 방법을 여러분의 적군을 구현할 때 응용할 수 있습니다. 플레이어에 다가오는 적이 있을 때, 죽은 적군을 삭제하는 대신 화면에서 보이지 않는 위치로 이동시킨 뒤 비활성화하고, 나중에 필요할 때 다시 사용합니다.

같은 방식으로 여러분의 탄약에 사용 가능합니다. 플레이어나 NPC가 발사한 총알을 삭제하지 않고 비활성화 합니다. 그리고 다시 총알을 발사할 때 총 앞부분에 총알을 위치 시킨뒤, 적절한 속도를 설정하고 활성화 합니다.

메모리 레벨에서 풀링pooling 하게 되면, 운영체제에 의해서 유지해야 하는 메모리의 크기에 상관없이, 항상 같은 크기의 메모리가 유지됩니다. 그 결과, 전체 메모리에서 작은 메모리가 삭제되는 일이 없습니다.

 

RonnieJ

프리랜서 IT강사로 활동하고 있습니다. 게임 개발, 웹 개발, 1인 기업, 독서, 책쓰기에 관심이 많습니다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다