유니티 가비지 컬렉션 최적화하기 1 – 번역
유니티에서 가비지 컬렉션 최적화하기 – 번역
– 원문 링크 –
개요 (Introduction)
게임이 실행되면, 데이터를 저장하기 위해서 메모리를 사용합니다. 그런데 이 데이터가 더 이상 필요하지 않게되면, 재사용(reuse)이 가능하도록 데이터를 저장하는데 사용된 메모리를 비웁니다.
가비지(Garbage)란 데이터를 저장하기 위해서 따로 설정했다가 더이상 사용하지않는 메모리를 지칭하는 용어입니다.
가비지 컬렉션(Garbage Collection)이란 이러한 메모리를 재사용이 가능하도록 처리하는 과정을 나타내는 용어입니다.
유니티는 메모리를 관리하는 한 부분으로, 가비지 컬렉션을 사용합니다.
가비지 컬렉션이 너무 자주 발생하거나, 가비지 컬렉션에서 처리해야할 일이 너무 많으면 게임의 퍼포먼스(perfomance, 성능)가 매우 낮게 나옵니다.
즉, 가비지 컬렉션은 퍼포먼스 문제를 발생시키는 주요 원인입니다.
이번 글에서, 가비지 컬렉션이 동작하는 방법과 언제 가비지 컬렉션이 발생하는지에 대해서 살펴봅니다.
그리고 가비지 컬렉션이 게임에 미치는 영향을 최소한으로 줄여서 메모리를 효율적으로 사용할 수 있는 방법에 대해서 살펴봅니다.
유니티에서 메모리를 관리하는 방법에 대한 간략한 소개 (A brief introduction to memory management in Unity)
가비지 컬렉션이 동작하는 방법과 언제 발생하는지에 대해서 이해하려면, 유니티에서 메모리 사용을 어떻게 처리하는지에 대해서 먼저 이해해야 합니다.
유니티에서 메모리를 관리하는 방법을 자동 메모리 관리라고 부릅니다.
다시 말해, 메모리를 관리하는 방법을 유니티에서 명시적으로, 상세하게 알려줄 필요가 없습니다. 유니티가 우리를 위해서 이 부분을 알아서 처리합니다.
가장 기본적인 수준에서, 유니티에서 자동 메모리 관리는 다음과 같은 방식으로 동작합니다:
- 유니티는 두 가지 메모리풀(Memory Pool)에 대한 접근권한을 갖습니다: 바로 스택(Stack)과 힙(Heap)입니다. 스택은 작은 단위의 데이터를 단기간 저장하는데 사용됩니다. 힙은 좀더 큰 데이터를 장기간 저장하는데 사용됩니다.
- 변수가 생성되면, 유니티는 스택(Stack) 또는 힙(Heap)으로부터 메모리 블록(메모리 공간)을 요청합니다.
- 변수가 스코프 내에(여전히 코드에서 접근가능한 상태)있는 경우, 이 변수에 할당된 메모리는 사용가능한 상태로 남습니다. 이때 이 메모리를 할당된 상태(allocated)라고 말합니다. 스택 메모리에 저장된 변수를 스택 상의 오브젝트라고 하며, 힙 메모리에 저장된 변수를 힙 상의 오브젝트라고 합니다.
- 변수가 스코프를 벗어나면, 이를 위해서 할당되었던 메모리는 더이상 필요가 없습니다. 그리고 이 메모리가 할당되었던 메모리 풀(Memory Pool, 스택 또는 힙)로 반환될 수 있습니다. 메모리가 메모리 풀에 반환되면, 이 메모리를 해제된 상태(deallocated)라고 말합니다. 스택에서 할당된 메모리는 이 메모리가 스코프를 벗어나는 즉시 해제됩니다. 반면에, 힙에서 할당된 메모리는 이 메모리가 스코르를 벗어난 시점에 할당된 상태로 남습니다.
- 가비지 컬렉터(Garbage Collector)는 힙 메모리에서 현재 사용되지 않는 메모리를 식별하고, 이 메모리를 해제됩니다. 가비지 컬렉터는 일정 기간마다 실행되어 힙 메모리를 청소합니다.
이제 메모리관리에 대한 기본적인 이벤트 흐름에 대해서 살펴봤습니다. 이어서 스택 메모리의 할당과 해제가 힙 메모리의 할당과 해제와 어떻게 다른지에 대해서 좀 더 자세히 살펴보겠습니다.
스택의 할당과 해제 과정에서는 어떤 일이 발생하나요? (What happens during stack allocation and deallocation?)
스택 메모리의 할당 및 해제는 빠르고 간단합니다. 이는 스택이 작은 데이터를 단기간 저장하는데 사용되기 때문입니다.
할당과 해제가 발생하는 시점과 그 메모리의 크기는 항상 예측가능합니다.
스택은 스택 데이터 타입과 유사하게 동작합니다: 스택은 특정 요소들의 집합입니다.
스택 메모리에서 이 요소들은 메모리 블록(Memory Block)이며, 엄격한 순서에 따라서 메모리 블록을 추가하거나 제거할 수 있습니다.
이런 단순함과 엄격함이 스택 메모리를 빠르게 동작할 수 있도록 만듭니다: 스택에 변수가 저장되면, 이 변수를 저장하기 위한 메모리는 스택의 “끝”에 할당됩니다.
스택 변수가 스코프에서 벗어나면, 이 변수를 저장하는데 사용된 메모리는 그 즉시 재사용이 가능하도록 스택에 반환됩니다.
힙 할당 과정에서는 어떤 일이 발생하나요? (What happens during a heap allocation?)
힙 메모리의 할당 과정은 스택 할당 과정에 비해서 훨씬 복잡합니다.
힙은 데이터의 단기간 저장과 장기간 저장에 모두 사용되며, 저장될 수 있는 데이터의 타입이 매우 많고, 그 크기도 다양하기 때문에 힙 메모리의 할당과정이 훨씬 복잡한 것입니다.
힙 메모리의 할당과 해제는 예측가능한 시점에 발생하지 않으며, 할당과 해제가 발생하는 메모리 블록의 크기가 매우 다양합니다.
힙 메모리에 변수가 생성되면, 다음의 과정이 발생합니다:
- 첫째로, 유니티는 힙 메모리상에 사용가능한 메모리 공간이 있는지 확인합니다. 힙에 사용가능한 충분한 공간이 있는 경우에, 변수를 저장하기 위한 메모리가 할당됩니다.
- 힙에 사용가능한 메모리 공간이 부족한 경우, 유니티는 가비지 컬렉터를 실행해서 현재 사용하지 않는 메모리를 비웁니다. 이 작업은 시간이 많이 소모될 수 있는 과정입니다. 메모리를 비우는 과정이 끝난 뒤에, 변수를 저장하는데 필요한 충분한 메모리가 확보되면, 해당 메모리가 할당됩니다.
- 가비지 컬렉션 작업 이후에도 메모리가 충분하지 않으면, 유니티는 힙 메모리의 양을 증가시킵니다. 이 작업 역시 시간이 많이 소모될 수 있는 과정입니다. 이제 변수를 저장하기 위한 메모리가 할당됩니다.
힙의 할당 과정은 시간이 많이 소모됩니다. 특히, 가비지 컬렉터가 실행되어야 하거나 힙이 확장되어야 하는 상황에서는 더욱 시간이 많이 소모됩니다.
가비지 컬렉션 과정 중에는 어떤 일이 발생하나요? (What happens during garbage collection?)
힙에 할당된 변수가 스코프를 벗어나면, 이 변수를 저장하는데 사용된 메모리는 이때 바로 해제되지 않습니다. 이렇게 사용되지 않는 힙 메모리는 가비지 컬렉터가 실행되어야만 해제됩니다.
가비지 컬렉터가 실행될때마다, 아래 설명된 과정이 발생합니다:
- 가비지 컬렉터가 힙 상의 모든 오브젝트를 검사합니다.
- 가비지 컬렉터가 오브젝트 참조(references)값을 모두 확인해서 해당 오브젝트가 힙 상에서 여전히 스코프내에 있는지(사용 중인지) 확인합니다.
- 스코프를 벗어난 오브젝트는 모두 삭제를 위해서 플래그(flag)로 표시해둡니다.
- 플래그로 표시된 오브젝트가 삭제되어, 할당되었던 메모리가 힙에 반환됩니다.
가비지 컬렉션을 처리하는데 시간이 많이 걸릴 수 있습니다.
힙 상의 오브젝트가 더 많을수록, 오브젝트 참조값이 더 많을수록, 처리해야하는 일이 더 많아집니다.
가비지 컬렉션은 언제 발생하나요? (When does garbage collection happen?)
가비지 컬렉터는 아래 설명된 3가지 상황에서 실행될 수 있습니다:
- 사용가능한 메모리가 충분하지 않은 상황에서 힙 할당이 발생하면, 가비지 컬렉터가 실행됩니다.
- 가비지 컬렉터는 가끔씩 실행됩니다(얼마나 자주 실행되는지 여부는 플랫폼마다 다릅니다).
- 가비지 컬렉터를 수동으로 강제해서 실행시킬 수 있습니다.
가비지 컬렉션은 자주 실행될 수 있습니다.
힙을 할당해야하는데 사용가능한 메모리 공간이 충분하지 않으면 가비지 컬렉터가 실행됩니다.
즉, 힙 메모리의 할당과 해제를 자주하게되면 가비지 컬렉션이 자주 발생할 수 있습니다.
내용 끝까지 읽어주셔서 감사합니다.
배너 클릭은 저에게 많은 힘이 됩니다.
감사합니다 🙂