유니티에서의 메모리 관리.
유니티에서 사용하는 메모리는 3가지 종류의 영역이 있다.
code영역
유니티 엔진과 라이브러리, 게임코드가 컴파일 되어서 디바이스의 메모리 코드 영역에 로딩되게 된다.
크게 최적화 할 필요가 없음. 코드 파일 용량이라고 해 봤자 어차피 얼마 안된다.
Managed Heap 영역
Mono(.Net Framework의 오픈소스버전)가 관리하는 영역.
instantiated object, variable, new 키워드로 할당한 메모리, 들이 거주하게 되는 영역.
Managed인 이유는 Mono Framework이 이 영역의 메모리를 할당하거나 해제하면서 관리하기 때문.
유니티 레퍼 참고
오브젝트나 string 또는 배열이 생성될 때, 이를 저장하기 위해 요구되는 메모리는
Heap이라고 불리는 중앙 풀에서 할당되어진다.
아이템을 더 이상 사용하지 않을 떄, 한번 차지했던 메모리를 되찾아서
다른곳에 사용될 수 있다.
과거에는 프로그래머들이 힙 메모리의 블럭을 할당하고 해제하기 위해
명시적으로 함수를 호출하였다.
요즘은, 유니티의 Mono엔진과 같은 런타임 시스템이 자동으로 메모리를 관리해 준다.
자동 메모리 관리는 명시적으로 할당/해제를 하는 코딩적 노력이 적고
메모리 누수(할당하고 해제하지 않은 경우)가 발생할 가능성이 많이 감소시켜준다.
GC에게 먹이를 주지 않는 몇가지 방법을 보자.
1. string의 조합대신 mono 라이브러리가 제공하는 system.text.stringbuilder를 사용하자.
2. string의 업데이트를 최대한 줄이자.
void Update() { string scoreText = "score : " + score.Tostring();}
위 코드는 매번 string을 업데이트 시킨다.
void Update() { if(score != oldscore) string scoreText = "score : " + score.Tostring();}
위 코드는 실제 값이 다를경우만 업데이트 시킨다.
3. 함수가 배열값을 반환할 때 문제가 발생할 수 있다.
float[] RandomList(int val)
{
var result = new float[val];
for(int i=0; i<val; i++)
result[i] = Random.value;
return result;
}
위의 함수는 새 배열을 만들어 값을 채울 때 무척 우아하고 편하다.
하지만 반복적으로 호출되면 매번 새로운 메모리가 할당된다.
배열이 무척 커질 수 있기 때문에 사용가능한 힙 공간이 빠르게 소모되어
GC 빈도가 높아 질 수 있다.
이를 피하는 방법은 배열의 레퍼런스를 사용하는 것이다.
배열을 함수의 인자로 전달하면 함수 내에서 배열을 수정할 수 있고
함수의 리턴 후에 결과값도 남는다. 아래에 알맞게 수정한 결과물을 참고하자.
void RandomList(float[] valArray)
{
for(int i=0; i<valArray.Length; i++)
valArray[i] = Random.value;
}
위 함수는 기존 함수보다 우아하지는 않지만(초기 할당이 호출하는 쪽에서 필요함)
함수 호출이 될 때마다 새로운 가비지가 생성되는 것은 없다.
4. 오브젝트 풀링 시스템을 사용하자.
5. 값만 저장하는 데이터 타입은 class보다는 구조체를 사용하자.
struct는 stack에 생성되기 때문에 GC에 들어가지 않는다.
GC의 호출로 인해 프레임이 떨어지는 것을 최소화 히기 위한 방법
- Object Recycling 기법 : 다시 써야 한다면 Destroy보다는 Active을 on off 시키자.
- GC를 막거나 컨트롤 할 수는 없지만 미리 GC를 호출할 수는 있다.
좋은 타이밍에 GC를 호출해 주면 플레이 타임에 프레임 하락을 유발하는 일이 적어진다.
- 작은 Heap과 빠르고 빈번한 GC를 할 건지 큰 Heap과 느리지만 빈번하지 않은 GC를 할건지
자신의 프로젝트에 맞게 선택.
Native Heap 영역
유니티 엔진이 OS에서 메모리를 할당받아 texture, sound, effect, level data등을 저장하는 영역.
유니티 엔진이 Mono가 메모리를 관리하는 것 처럼 현재 scene에서 필요 없게 된
리소스가 차지하는 메모리 영역을 해제하는 작업을 수행하기도 하지만 manual하게 할당된
리소스에 대해서는 유니티가 관리를 하지 못하므로 부주의 하다면 심각한 메모리 부족사태에 이를 수 있다.
일반적으로 Scene을 로드할 때 Scene에 포함된 Asset들은 같이 메모리 상에 로드되고
Scene이 끝나서 다음 Scene으로 넘어갈 경우 이전 Scene의 리소스중 다음 Scene에 사용되지 않는 것은
메모리상에서 자동 해제 되는데 DontDestroyOnLoad일 경우는 지워지지 않는다.
그러므로 DontDestroyOnLoad로 지정한 GameObject와 그 하위 오브젝트가 무거운 asset을
가지고 있거랑 링크하고 있지 않도록 해야 한다.
또 다른 경우는 script 내에서 scene object를 참조하고 있는 경우이다.
대부분 script의 참조는 scene이 전환됨에 따라 같이 파괴되지만
Scene이 전환되더라도 남아있는 GameObject가 있고 해당 GameObject가 Script를 가지고 있는데
이 Script 내에서 다른 Asset(ex: sound, texture...)에 대한 참조를 유지하고 있는 경우
리소스가 메모리상에 남아 메모리 자원부족에 이르게 할 위험이 있다.
singleton인스턴스가 자원에 대한 참조를 가지고 있는 경우도 마찬가지 문제가 발생할 수 있다.
GC.Collect()
사용하지 않는(또는 레퍼런스가 없는)메모리 구역을 해제한다.
Resources.UnloadUnusedAssets()
이 함수는 Resource.Load()를 통하여 할당된 에셋을 해제한다.
Reference
-
'Optimizing > Unity' 카테고리의 다른 글
Dynamic Batching이 효율적일까? (0) | 2017.03.31 |
---|---|
유니티 텍스처 (0) | 2017.03.22 |
animation 최적화 (0) | 2017.03.04 |
Unity sound setting (0) | 2017.01.10 |
gc.markdependencies (0) | 2016.09.07 |