
프로파일링 도구는 데이터를 수집하고 분석하여 병목현상을 파악하거나 성능 지표를 결정하는 데 사용된다. 이러한 도구는 유니티 엔진에서 제공하는 것만으로도 여러 가지가 있다. 이 외에도 Xcode나 Android Studio와 같은 네이티브 호환 도구, GPU에 특화된 RenderDoc 등 다양한 도구가 존재한다. 따라서 각 툴의 특징을 이해하고 적절히 선택하는 것이 중요하다. 이 장에서는 각 도구에 대한 소개와 프로파일링 방법에 대해 알아보고, 적절하게 사용할 수 있는 상태를 목표로 한다.
Unity는 에디터에서 애플리케이션을 실행할 수 있기 때문에 '실제 기기'와 '에디터' 모두에서 측정이 가능하다. 계측을 할 때 환경의 특징을 파악할 필요가 있다.
에디터에서 측정하는 경우, 시행착오를 빠르게 할 수 있다는 것이 가장 큰 장점이다. 하지만 에디터 자체의 처리 부하와 에디터가 사용하는 메모리 영역도 측정되기 때문에 측정 결과에 노이즈가 많이 발생한다. 또한 실제 기기와는 사양이 전혀 다르기 때문에 병목현상을 파악하기 어려워 결과가 다르게 나올 수도 있다.
따라서 프로파일링은 기본적으로 실제 기기 계측을 권장하고 있다. 단, '두 환경 모두에서 발생하는 경우'에 한해 작업 비용이 저렴한 에디터만으로 작업을 완료하는 것이 효율적이다. 대부분 두 환경 모두에서 재현되지만, 드물게 두 환경 중 하나에서만 재현되는 경우가 있다. 따라서 먼저 실제 기기로 현상을 확인한다. 다음으로 에디터에서도 재현을 확인한 후, 에디터에서 수정하는 흐름이 좋다. 물론 마지막에 실제 기기에서의 수정 확인은 필수이다.
Unity Profiler는 Unity 에디터에 내장된 프로파일링 툴이다. 이 툴은 1프레임마다 정보를 수집할 수 있으며, 측정할 수 있는 항목은 매우 다양하다. 각 항목을 프로파일러 모듈이라고 부르며, Unity 2020 버전에서는 14개의 항목이 존재한다. 이 모듈은 계속 업데이트되고 있으며, Unity 2021.2 버전에서는 Asset 관련 모듈과 File I/O 관련 모듈이 새롭게 추가되었다. 이처럼 Unity Profiler는 다양한 모듈이 있기 때문에 대략적인 성능의 모습을 파악하기 위한 최적의 도구라고 할 수 있다. 모듈 목록은 아래 표에서 확인할 수 있다.

이 모듈들은 프로파일러에 표시 여부를 설정할 수 있다. 단, 표시하지 않은 모듈은 측정되지 않는다. 반대로 모든 모듈을 표시하면 그만큼 에디터에 부하가 걸린다.

위 사진은 Profiler Modules의 표시/숨기기 기능이다.
다음으로 프로파일러 도구 전체에 공통적으로 적용되는 유용한 기능을 소개한다.

위 그림에서 '①'은 각 모듈이 측정하고 있는 항목이 나열되어 있다. 이 항목을 클릭하면 오른쪽 타임라인에 표시/숨기기를 전환할 수 있다. 필요한 항목만 표시하면 보기 편해진다. 또한 이 항목은 드래그하여 정렬할 수 있으며, 오른쪽 그래프는 정령된 순서대로 표시된다. '②'는 측정한 데이터를 저장, 불러오는 기능이다. 필요하면 측정 결과를 저장해두면 좋다. 저장되는 데이터는 프로파일러에 표시하고 있는 데이터만 저장된다.
이 책에서는 사용 빈도가 높은 CPU Usage 와 Memory 모듈에 대해 설명한다.
여기서는 Unity Profiler 에서 실기를 이용한 측정 방법에 대해서 설명한다. 빌드 전 작업과 애플리케이션 실행 후 작업 두 가지로 나누어 설명한다. 참고로 에디터에서 측정하는 방법은 실행 중에 측정 버튼을 누르기만 하면 되므로 자세한 설명은 생략한다.
<빌드 전 작업>
빌드전 작업은 Development Build를 활성화하는 것이다. 이 설정이 활성화되면 프로파일러와 연결이 가능해진다.
또한, 보다 상세한 측정을 위한 Deep Profile이라는 옵션도 있다. 이 옵션을 활성화화면 모든 함수 호출의 처리 시간이 기록되어 병목 현상을 일으키는 함수를 쉽게 파악할 수 있다. 단점은 측정 자체에 매우 큰 오버헤드가 발생하기 때문에 동작이 무거워지고, 메모리도 많이 소모된다는 점이다. 따라서 처리 시간이 매우 오래 걸리는 것처럼 보이지만, 일반 프로파일에서는 그다지 오래 걸리지 않을 수도 있으니 주의해야한다. 기본적으로 일반 프로파일으로는 정보가 부족할 때만 사용한다.
Deep Profile은 대규모 프로젝트 등으로 메모리를 많이 사용하는 경우, 메모리 부족으로 측정이 불가능할 수도 있다. 이 경우
3.1.2 CPU Usage절의보충 : Sampler에 대하여를 참고하여 자체적으로 측정 처리를 추가할 수 밖에 없다.
이러한 설정 방법은 스크립트에서 명시적으로 지정하는 방법과 GUI에서 하는 방법이 있다. 먼저 스크립트에서 설정하는 방법을 보자.
<예시> : 스크립트에서 Development Build를 설정하는 방법
BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions();
// 씬과 빌드 타겟 설정은 생략
buildPlayerOptions.options != BuildOptions.Development;
// Deep Profile 모드를 활성화하고 싶은 경우에만 추가
buildPlayerOptions.options != BuildOptions.EnableDeepProfilingSupport;
BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptins);
위 코드에서 중요한 점은 BuildOptions.Development 를 지정하는 것이다.
다음으로 GUI에서 설정하는 경우, Build Settings에서 아래 그림 같이 Development Build에 체크하여 빌드한다.

<애플리케이션 실행 후 수행해야 할 직업>
애플리케이션 실행 후 Unity Profiler 와 연결하는 방법은 '원격 연결'과 '유선 연결' 두 가지가 있다. 원격 연결은 유선 연결에 비해 환경의 제약이 많아 프로파일링이 원하는 대로 되지 않을 수 있다. 예를 들어, 동일한 와이파이 네트워크에 연결해야 한다거나, 안드로이드만 모바일 통신을 비활성화해야 한다거나, 다른 포트를 해제해야 한다거나 하는 등의 제약이 있다. 따라서 이 절에서는 절차가 간단하고 안정적으로 프로파일링할 수 있는 유선 연결에 대해 설명한다. 만약 원격 연결을 원한다면 공식 문서를 참고하는 것을 추천한다.
먼저 iOS의 경우 프로파일러에 접속하는 방법은 다음과 같다.
- Build Settings에서 Target Platform을 iOS로 변경
- 디바이스를 PC에 연결하고 Development Build 애플리케이션 실행
- Unity Profiler에서 연결 대상 단말 선택
- Record 시작

다음으로 Android이다.
- Build Setting에서 Target Platform을 Android로 변경
- 디바이스를 PC에 연결하고 Development Build 애플리케이션 실행
- adb forward 명령을 입력. (명령어에 대한 자세한 설명은 아래 참조)
- Unity Profiler에서 연결 대상 단말 선택
- Record 시작
adb forward 명령어에는 애플리케이션의 Package Name이 필요하다. 예를 들어 Package Name이 'kr.co.sample.app'이라면 다음과 같이 입력한다.
<예시> : adb forward 명령어
adb forward tcp:34999 localabstract:Unity-kr.co.sample.app
adb가 인식되지 않는 경우, adb의 경로를 설정해야 한다. 설정 방법은 웹상에 많이 나와 있으므로 생략한다.
간단한 문제 해결로 연결이 되지 않는 경우 아래를 확인해보자.
두 장치 공통
-> 실행한 애플리케이션의 오른쪽 하단에 Development Build 라는 표기가 있는지안드로이드의 경우
-> 단말기의 USB 디버깅이 활성화되어 있는지
-> adb forward 명령어에 입력한 패키지 이름이 올바른지 여부
-> adb devices 명령어를 입력했을 때 장치가 정상적으로 인식되는지 여부
참고로 Build And Run에서 직접 애플리케이션을 실행한 경우, 위에서 언급한 adb forward 명령이 내부적으로 수행된다. 따라서 측정 시 명령어 입력은 필요하지 않는다.
Autoconnect Profiler
빌드 설정에서는 Autoconnect Profiler 라는 옵션이 있다. 이 옵션은 앱 실행 시 에디터의 프로파일러에 자동 연결하기 위한 기능이다. 따라서 프로파일에 필수적으로 설정해야 하는 것은 아니다. 원격 프로파일링 시에도 마찬가지이다. WebGL만 이 옵션이 없으면 프로파일링을 할 수 없지만, 모바일에 관해서는 그다지 유용한 옵션은 아닐 것이다.
좀 더 자세히 설명하면, 이 옵션을 활성화하면 빌드 시 에디터의 IP 주소가 바이너리에 기록되어 시작 시 해당 주소로 접속을 시도하게 된다. 전용 빌드 머신 등에서 빌드하는 경우, 해당 머신에서 프로파일을 설정하지 않는 한 이 옵션은 필요하지 않는다. 오히려 애플리케이션 시작 시 자동 연결이 타임아웃(8초 정도)되는 대기 시간이 늘어날 뿐이다.
스크립트에서는 BuildOptions.ConnectWithProfiler라는 옵션 이름으로 되어 있어 필수인 것처럼 착각하기 쉬우므로 주의하자.
CPU Usage는 아래 그림과 같이 표시된다.

이 모듈을 확인하는 방법은 크게 두 가지로 나뉜다.
Hierarchy (Raw Hierarchy)
Timeline
먼저 Hierarchy뷰와 관련하여 표시 내용과 사용법에 대해 설명한다.
<Hierarchy 뷰>
Hierarchy 뷰는 다음 그림과 같이 표시된다.

이 뷰의 특징은 측정 결과가 목록 형태로 나열되어 있고, 헤더의 항목별로 정렬이 가능하다는 점이다. 조사를 할 때 목록에서 관심 있는 항목을 열어서 병목현상을 파악할 수 있다. 단, 표시되는 정보는 '선택된 스레드'에서 소요된 시간을 표시하는 것이다. 예를 들어, Job System이나 멀티스레드 렌더링을 사용하는 경우, 다른 스레드에서의 처리 시간은 포함되지 않는다. 확인하려면 아래 그림과 같이 스레드를 선택하면 확인할 수 있다.

다음으로 헤더의 항목에 대해 설명한다.

Calls는 여러 번의 함수 호출을 하나의 항목으로 묶어서 보기 편한 뷰가 된다. 하지만 모두 동일한 처리 시간인지, 아니면 그 중 하나만 처리 시간이 긴지는 알 수 없다. 이런 경우에 Raw Hierarchy 뷰를 이용한다. Raw Hierarchy 뷰는 반드시 Calls가 1로 고정된다는 점에서 Hierarchy뷰와 다르다. 아래 그림에서는 Raw Hierarchy뷰로 설정했기 때문에 동일한 함수 호출이 여러 개 표시되어 있다.

지금까지의 내용을 정리하면 Hierarchy뷰는 다음과 같은 용도로 사용한다.
- 처리 지연되는 병목 현상 파악, 최적화 (Time ms, Self ms)
- GC 할당 파악, 최적화 (GC Allocation)
이러한 작업을 할 때는 원하는 항목별로 내림차순으로 정렬한 후 확인하는 것을 권장합니다.
항목을 열어보면 깊은 계층 구조로 되어 있는 경우가 많다. 이때 Mac의 경우 Option키 (Windows의 경우 Alt 키)를 누른 상태에서 열면 모든 계층이 열립니다. 반대로 키를 누른 상태에서 항목을 닫으면 해당 계층 이하가 닫힌 상태가 된다.
<타임라인 보기>
또 다른 확인 방법인 타임라인 뷰는 다음과 같이 표시된다.

타임라인 뷰에서는 Hierarchy뷰의 항목이 박스로 시각화되어 있기 때문에 한눈에 부하가 걸리는 부분을 파악할 수 있다. 그리고 마우스 조작이 가능하기 때문에 계층 구조가 깊은 경우에도 드래그만으로 전체 내용을 파악할 수 있다. 또한, 타임라인에서는 굳이 스레드를 전환할 필요 없이 모두 표시되어 있다. 따라서 각 스레드에서 언제 어떤 처리가 이루어지고 있는지도 쉽게 파악할 수 있다. 이러한 특징으로 인해 주로 다음과 같은 용도로 사용한다.
- 처리 부하를 전체적으로 조망하고 싶다.
- 각 스레드의 처리 부하를 파악하여 튜닝하고 싶다.
Timeline이 적합하지 않은 것은 무거운 처리 순서를 파악하기 위한 정렬 작업이나 할당 총량을 확인하는 경우 등이다. 따라서 할당량 튜닝을 위해서는 Hierarchy뷰가 더 적합할 것이다.
<보충 : Sampler에 대하여>
함수 단위로 처리 시간을 측정하는 경우 두 가지 방법이 있다. 하나는 앞서 소개한 Deep Profile모드이다. 다른 하나는 스크립트에 직접 삽입하는 방법이다.
직접 삽입하는 경우 다음과 같이 작성한다.
using UnityEngine.Profiling;
/* ... 생략 ...*/
private void TestMethod()
{
for (int i = 0; i < 10000; i++)
{
Debug.Log("Test");
}
}
private void OnClickedButton()
{
Profiler.BeginSample("Test Method");
TestMethod();
Profiler.EndSample();
}
임베디드 샘플은 Hierarchy뷰, Timeline뷰 모두에 표시된다.

한 가지 더 주목할 만한 특징이 있다. 프로파일링 코드는 Development Build가 아닌 경우 호출 측이 비활성화되어 오버헤드가 0이 된다는 점이다. 향후 처리 부하가 늘어날 것 같은 부분은 미리미리 준비해 두는 것도 좋은 방법이다.
BeginSample메소드는 정적 함수이기 때문에 쉽게 사용할 수 있지만, 비슷한 기능을 가진 CustomSampler라는 것도 있다. 이는 Unity 2017버전 이후에 추가된 것으로, BeginSample보다 측정 오버헤드가 적어 보다 정확한 시간을 측정할 수 있다는 특징이 있다.
<예시> : CustomSampler를 이용한 방법
using UnityEngine.Profiling;
/* ... 생략 ...*/
private CustomSampler _samplerTest = CustomSampler.Create("Test");
private void TestMethod()
{
for (int i = 0; i < 10000; i++)
{
Debug.Log("Test");
}
}
private void OnClickedButton()
{
_samplerTest.Begin();
TestMethod();
_samplerTest.End();
}
차이점으로는 사전에 인스턴스를 생성해 두어야 한다는 점이다. CustomSampler는 측정 후 스크립트 내에서 측정 시간을 얻을 수 있는 것도 특징이다. 좀 더 정확도가 필요하거나, 처리 시간에 따라 경고 등을 주고 싶다면 CustomSampler를 사용하는 것이 좋다.
메모리 모듈은 아래 같이 표시된다.

이 모듈을 확인하는 방법은 두 가지가 있다.
- Simple 뷰
- Detailed 뷰
먼저 Simple 뷰에 대해 표시 내용과 사용법에 대해 설명한다.
<Simple 뷰>
Simple 뷰는 다음 그림과 같이 표시된다.

Total Used Memory
Unity가 할당받은 (사용 중인) 총 메모리 양
Total Reserved Memory
Unity가 현재 확보한 메모리의 총량. OS측에서 미리 일정량의 연속 메모리 영역을 풀로 확보해 두었다가 필요한 시점에 할당한다. 풀 영역이 부족해지면 다시 OS 측에 요청하여 확장한다.
System Used Memory
애플리케이션에서 사용하고 있는 메모리의 총량. 이 항목에서는 Total Reserved에서 측정되지 않은 항목 (플러그인 등)도 측정된다. 단, 이 항목도 모든 메모리 할당을 추적할 순 없다. 정확한 파악을 위해서는 Xcode와 같은 네이티브 호환 프로파일링 툴을 사용해야 한다.
위 그림 Total Used Memory 오른쪽에 기재된 항목의 의미는 다음과 같다.
- GC : 힙 영역에서 사용하고 있는 메모리 양. GC Alloc 등의 요인으로 증가한다.
- Gfx : Texture, Shader, Mesh 등에서 확보하고 있는 메모리 양.
- Audio : 오디오 재생에 사용되는 메모리 양.
- Vedio : 비디오 재생에 사용되는 메모리 양.
- Profiler : 프로파일링에 사용되는 메모리의 양.
용어에 대한 보충 설명으로, Unity 2019.2 버전 부터 'Mono'는 'GC'로, 'FMOD'는 'Audio'로 표기가 변경되었다.
위 그림에는 그 외에도 다음과 같은 애셋의 사용 개수와 메모리 확보량도 기재되어 있다.
- Texture
- Mesh
- Material
- Animation Clip
- Audio Clip
또한, 다음과 같은 오브젝트 수와 GC Allocation에 대한 정보도 있다.
Asset Count
로드된 애셋의 총 개수
Game Object Count
씬에 존재하는 게임 오브젝트 수
Scene Object Count
씬에 존재하는 컴포넌트, 게임 오브젝트 등의 총 개수
Object Count
애플리케이션에서 생성하고 로드한 모든 오브젝트의 총 개수. 이 값이 증가하면 어떤 오브젝트가 유출되었을 가능성이 높다.
GC Allocation in Frame
1프레임 내에서 Allocation이 발생한 횟수와 총량.
마지막으로 Simple뷰는 다음과 같은 용도로 사용된다.
- 힙 영역 및 Reserved의 확장 타이밍 파악 및 모니터링
- 각종 에셋 및 오브젝트 누수 여부 확인
- GC Allocation 모니터링
Unity 2021버전 이후로 Simple 뷰의 UI가 크게 개선되어 아래와 같이 변경되었다. GC가 Managed Heap으로 명칭이 변경된것과 같이 일부 변경된 점이 있다.

<Detailed 뷰>
Detailed 뷰는 다음 그림과 같이 표시된다.

이 표시 결과는 'Take Simple' 버튼을 눌러 해당 시점의 메모리 스냅샷을 얻을 수 있다. Simple 뷰와 달리 실시간 업데이트가 되지 않으므로, 표시를 갱신하려면 Take Sample을 다시 눌러야 한다.
위 그림을 보면 오른쪽에 'Referenced By'라는 항목이 존재한다. 현재 선택 중인 오브젝트를 참조하고 있는 오브젝트가 표시된다. 만약 유출된 애셋이 존재한다면, 오브젝트의 참조처 정보가 해결의 실마리가 될 수 있다. 이 표시는 'Gather object references'를 활성화한 경우에만 표시된다. 이 기능을 활성화하면 Take Sample시 처리 시간이 길어지지만, 기본적으로 활성화해 두는 것이 좋다.
Referenced By에 ManagedStaticReferences() 라는 표기를 볼 수 있다. 이것은 어떤 정적 오브젝트에서 참조되었다는 의미이다. 프로젝트에 익숙한 분이라면 이 정보만으로도 어느 정도 감을 잡을 수 있을 것이다. 그렇지 않다면 <3.5 Heap Explorer>를 사용하는 것이 좋다.
Detailed 뷰의 헤더 항목은 본 그대로의 의미이다. 조작 방법은 <3.1.2 Cpu Usage>의 <1. Hierarchy>뷰와 동일하며, 헤더별 정렬 기능이 있고, 항목이 계층적으로 표시되어 있다. 여기서는 Name 항목에 표시되는 최상위 노드에 대해 설명한다.
- Assets : 씬에 포함되지 않은 로드한 애셋.
- Not Saved : 코드에 의해 런타임에 생성된 애셋. 예를 들어, new Material()과 같이 코드에서 생성된 오브젝트이다.
- Scene Memory : 로드한 씬에 포함된 애셋.
- 기타 : 위의 항목이 아닌 오브젝트. 유니티가 다양한 시스템에서 사용하는 항목에 할당.
상위 노드 중 기타에 기재된 항목은 평소에는 익숙하지 않은 것들이 많다. 그 중에서도 알아두면 좋은 것들을 소개한다.
System.ExecutableandDlls
바이너리, DLL등에 사용된 할당량을 나타낸다. 플랫폼이나 단말기에 따라 획득하지 못할 수 있으며, 이 경우 0B로 처리된다. 공통 프레임워크를 사용하는 다른 애플리케이션과 공유하는 경우도 있으며, 프로젝트에 대한 메모리 사용량은 기재된 수치만큼 크지 않을 수 있다. 이 항목을 줄이는 데 급급하기보다는 Asset을 개선하는 것이 좋다. 줄이는 방법은 DLL이나 불필요한 Script를 줄이는 것이 효과적이다. 가장 간단한 방법은 Stripping Level을 변경하는 것이다. 단, 실행 시 타입이나 메소드가 누락될 위험이 있으므로 디버깅을 꼼꼼히 해야한다.
SerializedFile
AssetBundle 내 객체의 테이블과 타입 정보인 Type Tree 등의 메타 정보를 나타낸다. AssetBundle.Unload(true or false)로 해제할 수 있다. 가장 효율적인 방법은 애셋 로드 후 이 메타 정보만 해제하는 Unload(false)를 하는 것이지만, 해제 타이밍이나 리소스 참조 관리를 철저히 하지 않으면 리소스를 이중으로 로드하거나 쉽게 메모리 누수를 유발할 수 있으므로 주의해야 한다.
PersistenManager.Remapper
Remapper는 메모리 풀이 부족하면 두 배로 확장되어 줄어 들지 않는다. 너무 많이 확장되지 않도록 주의하자. 특히 AssetBundle을 많이 로드하면 매핑 영역이 부족하여 확장된다. 따라서 동시에 로드되는 파일 수를 줄이기 위해 불필요한 AssetBundle을 언로드하는 것이 좋다. 또한, 하나의 AssetBundle에 불필요한 애셋이 많이 포함되어 있다면, 그 자리에서 분할 하는 것이 좋다.
마지막으로 Detailed뷰는 다음과 같은 용도로 사용된다.
- 특정 타이밍의 메모리 상세 파악 및 튜닝
-불필요한 애셋이 있는지, 예상치 못한 애셋은 없는지- 메모리 누수 조사
Profile Analyzer는 Profiler의 CPU Usage에서 얻은 데이터를 보다 세밀하게 분석하기 위한 툴이다. Unity Profiler에서는 1 프레임 단위의 데이터만 볼 수 있었던 반면, Profile Analyzer는 지정한 프레임 구간을 기준으로 평균값, 중앙값, 최소값, 최대값을 얻을 수 있다. 프레임별로 편차가 있는 데이터를 적절히 다룰 수 있기 때문에 최적화를 할 때 개선 효과를 보다 명확하게 보여줄 수 있다. 또한, CPU Usage에서 할 수 없었던 측정 데이터 간 비교 기능도 있어 최적화 결과를 비교, 시각화하는 데 매우 유용한 툴이다.

이 도구는 Package Manager에서 설치가 가능하다. Unity에서 공식적으로 지원하므로 Packages를 Unity Registry로 변경하고 검색창에 'Profile'을 입력하면 나타난다. 설치 후 'Window -> Analysis -> Profile Analyzer'에서 툴을 실행할 수 있다.
Profile Analyzer는 실행 직후에는 아래 그림과 같이 표시된다.

기능으로는 'Single'과 'Compare' 두 가지 모드가 있다. Single 모드는 하나의 계측 데이터를 분석할 때 사용하고, Compare 모드는 두 개의 계측 데이터를 비교할 때 이용한다.
'Pull Data'는 Unity Profiler에서 측정한 데이터를 분석하게 하여 결과를 표시할 수 있다. 미리 Unity Profiler에서 계측을 해 두어야 한다.
'Save', 'Load'는 Profile Analyzer에서 분석한 데이터를 저장하고 불러올 수 있다. 물론 Unity Profiler의 데이터만 보관해 두어도 문제가 없다. 이 경우 Unity Profiler에서 데이터를 로드하고, Profile Analyzer에서 매번 Pull Data를 수행해야한다. 그 절차가 번거롭다면 전용 데이터로 저장해 두는 것이 좋다.
분석 결과 화면은 다음과 같은 구성으로 되어 있다. 여기서 마커라는 단어가 나오는데, 이는 처리의 명칭(메소드 명)을 의미한다.
- 분석 구간 설정 화면
- 표시 항목의 필터 입력 화면
- 마커의 중앙깂 Top 10
- 마커 분석 결과
- 프레임 요약
- 스레드 요약
- 선택 중 마커 요약
각각의 표시 화면에 대해 살펴보자.
<분석 구간 설정 화면>
각 프레임의 처리 시간이 표시되며, 초기 상태에서는 모든 프레임이 선택된 상태이다. 프레임 구간을 다음 그림과 같이 드래그하여 변경할 수 있으므로, 필요에 따라 조정할 수 있다.

<필터 입력 화면>
필터 입력 화면은 분석 결과를 필터링할 수 있는 화면이다.

각 항목은 다음과 같다.
- Name Filter : 검색하고자 하는 프로세스 이름으로 필터링한다.
- Exclude Filter : 검색에서 제외할 프로세스 이름으로 필터링한다.
- Thread : 선택 대상 스레드가 분석 결과에 표시된다. 다른 스레드의 정보가 필요하면 추가할 수 있다.
- Depth Slice : CPU Usage에서 Hierarchy의 계층 수이다. 다른 스레드의 정보가 필요하다면 추가할 수 있다.
- Analysis Type : Total과 Self를 전활할 수 있다. CPU Usage에서 소개한 헤더 항목과 동일한 내용이다.
- Units : 시간 표시를 밀리초 또는 마이크로초로 변경할 수 있다.
- Marker Columns : 분석 결과의 헤더 표시를 변경할 수 있다.
Depth Slice가 All인 경우, PlayerLoop라는 최상위 노드가 표시되거나 동일한 처리의 계층적 차이가 표시되어 보기 어려울 때가 있다. 이 경우 Depth를 2~3 정도로 고정하고, 렌더링, 애니메이션, 물리 연산 등의 서브 시스템이 표시될 정도로 설정하는 것이 좋다.
<마커의 중앙값 Top10>
이 화면은 각 마커의 처리 시간을 중앙값으로 정렬하여 상위 10개 마커만 표시한 화면이다. 상위 10개 마커가 각각 얼마나 많은 처리 시간을 차지하고 있는지 한눈에 알 수 있다.

<마커 분석 결과>
각 마커의 분석 결과를 확인할 수 있다. Marker Name에 기재된 처리명과 Median(중앙값), Mean(평균값)을 통해 개선해야 할 처리를 분석하는 것이 좋다. 헤더 항목에 마우스 포인터를 올리면 해당 항목에 대한 설명이 표시되므로, 내용을 잘 모르는 경우에는 참고하자.

평균값과 중앙값
평균값은 모든 값을 더한 후 데이터 개수로 나눈 값이다. 반면, 중앙값은 정렬된 데이터의 중앙에 위치한 값을 말한다. 데이터가 짝수일 경우, 중앙에 있는 앞뒤의 데이터에서 평균값을 구한다.
평균값은 극단적으로 값이 떨어져 있는 데이터가 있으면 영향을 받기 쉬운 특성이 있다. 스파이크가 자주 발생하거나 샘플링 수가 충분하지 않은 경우, 중앙값을 참고하는 것이 더 나은 경우가 있을 수 있다.
아래 그림은 중앙값과 평균값의 차이가 큰 예시이다.
이 두 값의 특징을 알고 데이터를 분석하자.
<프레임 요약>
이 화면에는 측정한 데이터의 프레임 통계 정보가 표시된다.

분석 중인 프레임의 구간 정보 및 값의 변동 정도를 박스그래프(Boxplot)와 히스토그램을 이용하여 표시한다. 박스그래프는 사분위수 (quartile number)를 이해해야 한다. 사분위수란 데이터를 정렬한 상태에서 아래 표와 같이 값을 정한 것이다.
| 이름 | 설명 |
|---|---|
| 최소값 (Min) | 제일 작은 값 |
| 1 사분위수 (Lower Quartile) | 최소값에서 25% 위치에 있는 값 |
| 중앙값 (Median) | 최소값에서 50% 위치에 있는 값 |
| 상위 사분위수 (Upper Quartile) | 최소값에서 75% 위치에 있는 값 |
| 최대값 (Max) | 제일 큰 값 |
25%에서 75%구간을 박스로 묶은 것을 박스형 그래프라고 한다.

히스토그램은 가로축에 처리 시간, 세로축에 데이터 수를 나타내며, 이 역시 데이터의 분포를 파악하는 데 유용하다. 프레임 요약에서는 커서를 가져가면 구간과 프레임 수를 확인할 수 있다.

이러한 도표의 보기 방법을 이해한 후, 특징을 분석하면 좋다.
<스레드 요약>
이 화면은 선택한 스레드의 통계를 보여주는 화면이다. 각 스레드에 대한 박스히스토그램을 볼 수 있다.

<선택 중인 마커 요약>
<마커 분석 결과> 화면에서 선택 중인 마커에 대한 요약이다. 선택 중인 마커에 대한 처리 시간이 박스그래프 그림과 히스토그램으로 표현된다.

이 모드에서는 두 개의 데이터를 비교할 수 있다. 상하 각 데이터별로 분석할 구간을 설정할 수 있다.

화면 사용법은 Single모드와 크게 다르지 않지만 아래 그림과 같이 'Left'와 'Right'라는 단어가 다양한 화면으로 나타난다.

이것은 어느 쪽의 데이터인지 표시하는 것으로 위로 두번째 그림에 표시된 색상과 일치한다. Left는 상단의 데이터, Right는 하단의 데이터이다. 이 모드를 이용하면 튜닝 결과의 좋고 나쁨을 쉽게 분석할 수 있다.
Frame Debugger는 현재 표시되고 있는 화면이 어떤 흐름으로 렌더링되고 있는지 분석할 수 있는 도구이다. 이 도구는 기본적으로 에디터에 설치되어 있으며 'Window -> Analysis -> Frame Debugger'에서 열 수 있다.
에디터와 실제 기기에서 사용할 수 있다. 실제 기기에서 사용할 때는 Unity Profiler와 마찬가지로 'Development Build'로 빌드된 바이너리가 필요하다. 애플리케이션을 실행하고 장치 연결 대상을 선택한 후 'Enable'을 누르면 드로잉 명령어가 표시된다.

'Enable'을 누르면 다음과 같은 화면이 나타난다.

왼쪽 프레임은 1개의 항목이 하나의 드로잉 명령어이며, 위에서부터 순서대로 발행된 명령어들이 나열되어 있다. 오른쪽 프레임은 드로잉 명령어에 대한 상세 정보이다. 어떤 Shader가 어떤 프로퍼티와 함께 처리되었는지 알 수 있다. 이 화면을 보면서 다음과 같은 것을 염두에 두고 분석을 진행하면 좋다.
- 불필요한 명령어가 없는지
- 드로잉 배칭이 제대로 작동하고 있는지, 정리할 수 없는지
- 드로잉 대상의 해상도가 너무 높지 않은지
- 의도하지 않은 Shader를 사용하고 있지 않은지
바로 이전에 소개한 위 그림의 오른쪽 테두리 내용을 자세히 설명한다.
<조작 패널>
먼저 상단 부분에 있는 조작 패널에 대해 알아보자.

RT 0라고 표시된 부분은 여러 개의 렌더링 타겟이 존재할 때 변경할 수 있다. 특히 멀티 렌더 타겟을 이용하고 있을 때 각각의 렌더링 상태를 확인 할 때 유용하게 사용할 수 있다. Channels는 RGBA 모두 또는 하나의 채널만 표시할 것인지 변경할 수 있다. Levels는 슬라이더로 되어 있으며, 렌더링 결과의 밝기를 조절할 수 있다. 예를 들어 앰비언트나 간접조명 등 렌더링 결과가 어두울 때 밝기를 조절하여 보기 좋게 만드는 데 유용하다.
<그리기 개요>
이 영역에서는 드로잉 대상의 해상도와 형식에 대한 정보를 확인할 수 있다. 분명 높은 해상도의 렌더링 대상이 있다면 바로 알아차릴 수 있다. 이외에도 사용 중인 Shader 이름, Cull 등의 Pass 설정, 사용 중인 키워드 등을 알 수 있다. 하단에 기재된 'Why this~'라는 문장은 왜 드로잉을 일괄 처리하지 못했는지를 알려주는 문구이다. 아래 그림의 경우, 첫 번째 드로잉 콜을 선택했기 때문에 배치할 수 없다고 나와있다. 이렇게 세세하게 원인이 기재되어 있기 때문에, 배치에 대한 고민을 하고 싶을 때 이 정보를 참고하여 조정하면 좋다.

<Shader 속성 상제 정보>
이 영역은 그리는 Shader의 프로퍼티 정보가 기재되어 있다. 디버깅 시 유용하다.

프로퍼티 정보에 표시된 Texture2D가 어떤 상태인지 자세히 확인하고 싶을 때가 있다. 이때 MAC의 경우 Command키 (Window의 경우 Control키)를 누른 상태에서 이미지를 클릭하면 확대할 수 있다.
Memory Profiler는 Preview Package로 유니티가 공식적으로 제공하는 툴이다. Unity Profiler의 Memory 모듈과 비교했을 때, 주로 다음과 같은 장점이 있다.
- 캡처한 데이터가 스크린샷과 함께 로컬에 저장된다.
- 각 카테고리의 메모리 점유량이 시각화되어 알기 쉽다.
- 데이터 비교 기능
Memory Profiler는 v0.4 이후와 그 이전 버전에서 UI가 많이 바뀌었다. 여기서는 작성 시점의 최신 버전인 v0.5를 사용한다. 또한 v0.4 이상 버전에서 모든 기능을 이용하려면 Unity 2020.3.12f1이상의 버전이 필요하다. 또한 v0.4와 v0.5는 언뜻 보면 비슷해 보이지만, v0.5에서 기능이 대폭 업데이트되었다. 특히 오브젝트의 참조 관계 추적이 매우 쉬워졌기 때문에 기본적으로 v0.5 이상 사용을 권장한다.

Unity 2020에서 Preview 버전의 패키지는 'Project Settings -> Package Manager'에서 'Enable Preview Packages'를 활성화해야 한다.

이후 Unity Registry의 Package에서 Memory Profiler를 설치한다. 설치 후 'Window -> Analysis -> Memory Profiler'에서 툴을 실행할 수 있다.

또한 Unity 2021버전 이상에서는 패키지 추가 방법이 변경되었다. 추가하려면 'Add Package by Name'을 누르고 'com.unity.memoryprofiler'를 입력해야 한다.

Memory Profiler는 크게 4가지 요소로 구성되어 있다.
- 툴바
- Snapshot Panel
- 측정 결과
- Detail Panel
각 영역에 대한 설명을 제공한다.
<툴바>

위 그림은 Header의 캡쳐이다. 1번 버튼으로 측정 대상을 선택할 수 있고 2번 버튼으로 해당 시점의 메모리를 측정한다. 옵션으로 측정 대상을 Native Object로만 설정하거나, 스크린샷을 비활성화할 수 있다. 왠만해서 기본설정으로도 문제가 없을 것이다. 3번 버튼은 측정된 데이터를 불러올 수 있는 버튼이다. 그 외 'Snapshot Panel'과 'Detail Panel'을 누르면 좌우에 있는 정보 패널을 표시/숨길 수 있다. Tree Map의 표시만 보고 싶을 때는 숨기는 것이 좋다. 또한 '?'를 누르면 공식 문서를 열 수 있다.
측정과 관련하여 한 가지 주의할 점이 있다. 계측 시 필요한 메모리가 새로 확보되고 이후 해제되지 않는다는 점이다. 하지만 무한정 늘어나는 것은 아니며, 몇 번 측정하면 어느 정도 안정화된다. 측정 시 메모리 확보량은 프로젝트의 복잡도에 따라 달라 질 수 있다. 이 전제를 모르면, 부풀어 오른 메모리 사용량을 보고 누수가 있을 거라고 착각할 수 있으니 주의해야한다.
<Sanpshot Panel>

Snapshot Panel에는 측정한 데이터가 표시되며, 어떤 데이터를 볼지 선택할 수 있다. 이 데이터는 앱 시작부터 종료까지를 1세션으로 간주하여 세션별로 정리되어 있다. 또한, 측정 데이터를 마우스 오른쪽 버튼으로 클릭하면 삭제 및 이름 변경이 가능하다.
상단에 'Single Snapshot', 'Compare Snapshots'가 있다. Compare Snapshots를 누르면 측정 데이터를 비교하는 UI로 표시가 바뀐다.
'A'는 Single Snapshot에서 선택한 데이터, 'B'는 Compare Snapshots에서 선택한 데이터이다. 교체 버튼을 눌러 'A'와 'B'를 교체할 수 있으므로, 굳이 Single Snapshot화면으로 돌아가지 않고도 전환할 수 있다.
<측정 결과>
측정 결과는 'Summary', 'Objects and Allocations', 'Fragmentation'의 세 가지 탭이 있다. 이번 절에서는 자주 사용하는 Summary를 설명하고, 나머지는 보충 설명으로 간략히 다룬다. Summary의 화면 상단은 Memory Usage Overview라는 영역으로, 현재 메모리의 개요가 표시되어 있다. 항목을 누르면 Detail Panel에 설명이 표시되므로, 모르는 항목은 확인하면 좋다.

다음으로 화면 중앙은 Tree Map이라고 불리며, 오브젝트의 카테고리별로 메모리 사용량을 그래픽으로 표시한다. 각 카테고리를 선택하면 카테고리 내 오브젝트를 확인할 수 있다. 아래 그림에서는 Texture2D 카테고리를 선택한 상태이다.

그리고 화면 하단은 Tree Map Table이라고 한다. 여기에는 오브젝트 목록이 테이블 형태로 나열되어 있다. 표시 항목은 Tree Map Table의 헤더를 눌러 그룹화, 정렬, 필터링을 할 수 있다.

특히 Type을 그룹화하면 분석이 쉬워지므로 적극적으로활용하자.

또한 Tree Map에서 카테고리를 선택하면 해당 카테고리 내의 객체만 표시하는 필터가 자동으로 설정된다.

마지막으로 Compare Snapshots를 사용했을 때의 UI 변화에 대해 설명한다. Memory Usage Overview에는 각 오브젝트의 차이점이 표시된다.

또한 Tree Map Table에는 Header에 Diff라는 항목이 추가된다. Diff에는 다음과 같은 유형이 있다.
| Diff | 설명 |
|---|---|
| Same | A,B 동일 객체 |
| Not in A (Deleted) | A에는 있지만 B에는 없는 객체 |
| Not in B (New) | A에는 없지만 B에는 있는 객체 |
이러한 정보를 보면서 메모리가 증감하고 있는지 확인할 수 있다.
<Detail Panel>
이 패널은 선택한 오브젝트의 참조 관계를 추적하고 싶을 때 사용한다. 이 Referenced By를 확인하면 참조를 계속 잡고 있는 원인을 파악할 수 있다.

하단의 Selection Details라는 부분에는 오브젝트에 대한 자세한 정보를 확인할 수 있다. 그 중 'Help'라는 항목에는 해제하는 방법에 대한 조언 등이 나와 있다. 어떻게 해야 할지 모르겠다면 한 번 읽어보는 것을 추천한다.

<추가 : Summary 이외의 측정 결과에 대해>
'Object and Allocations'는 Summary와 달리 할당 등 좀 더 자세한 내용을 테이블 형태로 확인할 수 있다.

'Fragmentation'은 가상 메모리의 상황을 시각화해 주기 때문에 조각화 조사에 활용할 수 있다. 메모리 주소 등 직관적이지 않은 정보가 많기 때문에 이용 난이도가 높을 수 있다.

또한 Memory Profiler v0.6부터 'Memory Breakdowns'라는 새로운 기능이 추가되었다. Unity 2022.1버전 이상부터 필수적으로 적용되며, TreeMap을 목록으로 볼 수 있고, Unity Subsystems와 같은 오브젝트 정보도 확인할 수 있게 되었다. 이 외에도 중복 가능성이 있는 오브젝트를 확인할 수 있게 되었다.

Heap Explorer는 개인 개발자 Peter77의 오픈소스 툴이다. 이 도구는 Memory Profiler와 마찬가지로 메모리 조사를 할 때 많이 사용하는 도구이다. Memory Profiler는 0.4 이전 버전에서는 레퍼런스가 목록 형태로 표시되지 않아 추적하는 데 많은 노력이 필요했다. 0.5 이후 버전부턴 개선됐지만, 지원하지 않는 Unity 버전을 사용하는 사람도 있을 것이다. 그 때 대체할 수 있는 도구로 여전히 활용가치가 높아서 여기서 소개한다.

본 도구는 GitHub리포지토리에 기재된 Package URL's를 복사하여 Package Manager의 'Add Package from Git url'에서 추가한다. 설치 후 'Window -> Analysis -> Memory Profiler'에서 툴을 실행할 수 있다.
Heap Explorer의 툴바는 다음과 같다.

<좌우 화살표>
조작의 뒤로, 앞으로 이동을 할 수 있다. 특히 참조를 추적할 때 유용하다.
<File>
측정 파일을 저장, 불러올 수 있다. .heap이라는 확장자로 저장된다.
<View>
표시 화면을 전환할 수 있다. 다양한 종류가 있으니 관심이 있으면 문서를 확인하자.
<Capture>
계측을 수행한다. 단, 측정 대상은 Heap Explorer에서 변경할 수 없다. 대상 전환은 Unity Profiler 등 Unity에서 제공하는 툴에서 변경해야 한다. 또한 Save는 파일에 저장한 후 결과를 표시하고, Analyze는 저장하지 않고 표시한다. 또한, Memory Profiler와 마찬가지로 측정할 때 확보된 메모리는 해제되지 않으므로 주의해야 한다.

측정 결과 화면은 다음과 같다. 이 화면을 Overview라고 한다.

Overview에서 특히 신경 써야 할 카테고리는 초록색 선으로 표시된 Native Memory Usage와 Managed Memory Usage이다. Investigate 버튼을 누르면 각 카테고리의 세부 정보를 확인할 수 있다.
아래에서는 카테고리 세부 정보 표시 중 중요한 부분을 중심으로 설명한다.
<Object>
Native Memory를 Investigate한 경우, C++Objects가 이 영역에 표시된다. Managed Memory의 경우 C# Objects가 이 영역에 표시된다.

헤더 항목에 대해 몇 가지 보충 설명한다.
DDoL
Don't Destroy On Load의 약자이다. 씬을 전환해도 파괴하지 않는 오브젝트로 지정되어 있는지를 알 수 있다.
Persistent
영구적인 오브젝트인지 여부이다. 시작 시 Unity가 자동 생성하는 것이 이에 해당한다.
이후 소개하는 표시 영역은 위 그림의 오브젝트를 선택하면 업데이트된다.
<Referenced by>
대상 오브젝트의 참조 대상이 되는 오브젝트가 표시된다.

<References to>
대상 오브젝트의 참조 대상 오브젝트를 표시한다.

<Path to Root>
대상 오브젝트를 참조하는 루트 오브젝트가 표시된다. 메모리 누수를 조사할 때 어떤 참조를 가지고 있는지 파악할 수 있어 유용하다.

지금까지의 내용을 정리하면 다음과 같은 그림이 된다.

지금까지 소개한 것처럼 Heap Explorer는 메모리 누수 및 메모리 조사에 필요한 기능을 모두 갖추고 있습니다. 동작도 가볍기 때문에 이 도구의 활용을 고려해 보자.