작성일 :

병목을 찾는 도구

앞선 글들에서는 렌더링, 메모리, 물리, 파티클, 애니메이션에서 발생하는 주요 비용과 최적화 방법을 다뤘습니다. 렌더링에서는 드로우콜과 오버드로우를, 메모리에서는 할당과 에셋 크기를, 물리와 애니메이션에서는 시뮬레이션과 평가 비용을 각각 살펴보았습니다.

최적화 기법은 병목 위치가 확인되었을 때 의미가 있습니다. 프레임 드롭이 발생할 때, 원인이 같은 픽셀을 여러 번 그리는 오버드로우인지, GC(Garbage Collection) 가 실행되며 프레임 시간이 급등하는 스파이크인지, 물리 연산이 프레임을 소모하는 것인지 구분하지 못하면 최적화 작업은 추측에 기반한 시행착오가 됩니다. GPU 바운드인 게임에서 스크립트를 최적화해도 프레임 시간은 줄어들지 않고, CPU 바운드인 게임에서 셰이더를 단순화해도 마찬가지입니다.

프로파일러(Profiler)는 이 판단을 수치로 확인하기 위한 도구입니다. 프레임마다 CPU, GPU, 메모리 사용이 어느 영역에 집중되는지 보여주고, 최적화 전후의 변화를 같은 기준으로 비교할 수 있게 합니다.

이 글에서는 Unity에 내장된 Unity ProfilerFrame Debugger의 구조를 다룹니다. 각 모듈(CPU, GPU, Memory)의 데이터를 읽는 방법부터, Frame Debugger로 드로우콜(CPU가 GPU에 보내는 개별 렌더링 명령)을 시각적으로 분석하는 방법, 그리고 프로파일러 수치에서 반복적으로 나타나는 성능 패턴을 인식하는 방법까지 순서대로 살펴봅니다.


Unity Profiler 개요

Unity Profiler는 프레임별 실행 시간을 모듈별로 기록하는 Unity의 기본 성능 분석 도구입니다. Window → Analysis → Profiler 메뉴에서 열 수 있으며, 에디터 Play 모드나 연결된 빌드의 성능 데이터를 그래프와 표로 확인할 수 있습니다.

프로파일러 창은 모듈 목록, 타임라인 그래프, 상세 뷰로 나뉩니다. 왼쪽 모듈 목록에서 CPU, GPU, Memory 같은 분석 대상을 선택하면, 상단 타임라인 그래프에 프레임별 변화가 표시됩니다. 특정 프레임을 클릭하면 하단 상세 뷰에서 해당 프레임의 함수 호출, 메모리 사용량, 렌더링 단계 같은 세부 데이터를 확인할 수 있습니다.

에디터에서 측정한 데이터는 원인 탐색에는 유용하지만, 최종 성능 판단의 기준으로 삼기에는 한계가 있습니다. 인스펙터 갱신, Scene 뷰 렌더링, 에디터 UI 처리처럼 게임 실행과 직접 관련 없는 작업이 함께 기록되기 때문입니다. 실제 성능 판단은 대상 플랫폼의 빌드에서 측정한 데이터를 기준으로 삼는 편이 적절합니다. 빌드에 프로파일러를 연결하고 측정 환경을 준비하는 방법은 Part 2에서 다룹니다.


CPU 모듈

CPU 모듈은 한 프레임 안에서 어떤 작업이 CPU 시간을 사용했는지 보여줍니다. 게임 로직, 물리 시뮬레이션, 애니메이션 평가, 렌더링 준비처럼 많은 작업이 CPU에서 시작되므로, 프레임 시간이 길어졌을 때 가장 먼저 확인하는 모듈입니다.

Hierarchy 뷰

Hierarchy 뷰는 CPU 시간을 함수 호출 구조에 따라 트리 형태로 보여줍니다. 최상위에는 Unity의 메인 루프인 PlayerLoop(빌드 실행 시)나 EditorLoop(에디터 실행 시)이 표시되고, 그 아래로 Update, Physics, Rendering 같은 큰 단계와 각 스크립트 함수가 계층적으로 펼쳐집니다.

CPU 모듈 — Hierarchy 뷰 예시 함수 이름 Total (ms) Self (ms) GC Alloc ▼ PlayerLoop 16.7 0.0 0 B ▼ Update.ScriptRunBehaviour 8.3 0.0 0 B ▼ EnemyManager.Update() 5.1 0.8 0 B ▼ Enemy.UpdateAI() 3.2 1.4 0 B PathFinding.Calculate() 1.8 1.8 256 B Enemy.UpdateAnimation() 1.1 1.1 0 B PlayerController.Update() 2.5 2.5 128 B UIManager.Update() 0.7 0.7 64 B ▼ PreLateUpdate 3.1 0.0 0 B ... ▼ PostLateUpdate 5.3 0.0 0 B ▼ Rendering 4.8 0.1 0 B Camera.Render() 4.7 0.3 0 B ... ▼ = 펼친 노드 · 들여쓰기 = 호출 계층 · GC Alloc 굵은 글씨 = 매 프레임 힙 할당 발생


Hierarchy 뷰에서 먼저 봐야 할 값은 TotalSelf입니다. Total은 해당 항목과 그 아래에서 호출된 모든 하위 항목의 시간을 합친 값이고, Self는 하위 호출을 제외하고 해당 항목 자체에서 소비된 시간입니다.

예를 들어 EnemyManager.Update()의 Total이 5.1ms이고 Self가 0.8ms라면, EnemyManager.Update()가 직접 소비한 시간은 0.8ms입니다. 나머지 4.3ms는 그 안에서 호출된 Enemy.UpdateAI()Enemy.UpdateAnimation() 같은 하위 항목에 들어 있습니다.

Total은 비용이 큰 호출 경로를 찾을 때 유용하고, Self는 실제로 시간이 쌓인 함수를 찾을 때 유용합니다. Total이 높은데 Self가 낮다면 해당 항목에서 멈추지 말고, 트리를 더 펼쳐 Self가 높은 하위 항목까지 내려가면 병목 위치가 더 분명해집니다.

GC Alloc 컬럼

Hierarchy 뷰의 GC Alloc 컬럼은 선택한 프레임에서 각 함수가 관리 힙(Managed Heap)에 새로 할당한 메모리 크기를 보여줍니다. C# 객체, 문자열, 배열처럼 GC가 관리하는 메모리가 생성되면 이 값에 표시됩니다.

이 값은 현재 프레임의 실행 시간뿐 아니라, 이후 GC 스파이크로 이어질 수 있는 할당 경로를 찾는 데 사용합니다. 초기화나 씬 로드 중 한 번 발생하는 할당은 큰 문제가 아닐 수 있지만, Update()처럼 매 프레임 실행되는 경로에서 같은 할당이 반복되면 관리 힙이 빠르게 늘고 GC 실행 주기가 짧아질 수 있습니다.

따라서 GC Alloc 컬럼은 값이 0이 아닌 항목을 찾는 데서 끝나지 않고, 같은 함수에서 반복적으로 발생하는 할당인지 확인하는 데 의미가 있습니다. 반복 할당이 보이면 매번 새 객체를 만들고 있는지, 문자열을 조합하고 있는지, 배열이나 컬렉션을 호출마다 생성하고 있는지 추적합니다. 이후에는 오브젝트 풀링, 캐싱, 재사용 가능한 버퍼 같은 방식으로 할당을 줄입니다.

관리 힙과 GC의 동작 원리는 메모리 관리 (1) - 가비지 컬렉션의 원리에서 다룹니다.


Timeline 뷰

Hierarchy 뷰가 함수별 비용을 집계해서 보여준다면, Timeline 뷰는 같은 프레임을 시간 흐름에 따라 펼쳐 보여줍니다. 메인 스레드와 워커 스레드에서 작업이 어떤 순서로 실행되었는지, 어느 구간에서 대기나 병목이 생겼는지를 확인할 때 사용합니다.


CPU 모듈 — Timeline 뷰 예시 메인 스레드 A B 렌더 스레드 C 잡 스레드 1 D E 잡 스레드 2 F G A 스크립트 실행 (Update, 물리 등) B 렌더링 준비 (컬링, 정렬) C GPU 명령 제출 (드로우콜 실행) D 물리 잡 EF 애니 잡 G 컬링 잡 → 막대의 길이 = 실행 시간, 스레드별 작업이 병렬로 진행됨

Timeline 뷰에서는 프레임 안의 작업이 스레드별 행으로 나뉘어 표시됩니다. 대표적으로 메인 스레드, 렌더 스레드, Job System의 워커 스레드를 볼 수 있으며, 각 작업은 시간축 위의 막대로 나타납니다. 막대가 길수록 해당 작업의 실행 시간이 길고, 빈 구간이나 Wait 마커는 다른 작업을 기다리는 시간일 수 있습니다.

Hierarchy 뷰가 어떤 함수에 시간이 많이 쓰였는지 찾는 데 유리하다면, Timeline 뷰는 그 시간이 왜 길어졌는지 확인하는 데 유리합니다. 메인 스레드가 실제로 작업을 수행하고 있는지, 렌더 스레드나 워커 스레드의 결과를 기다리고 있는지, 반대로 다른 스레드가 메인 스레드의 명령을 기다리고 있는지를 같은 시간대에서 비교할 수 있습니다.

렌더링 관련 Wait 마커는 병목 후보를 좁히는 단서로 사용합니다. 메인 스레드의 Gfx.WaitForPresentOnGfxThread가 길다면 렌더 스레드가 GPU의 프레임 표시(Present) 완료를 기다리는 상황일 수 있으므로, GPU 부하나 VSync 대기를 함께 확인합니다. 반대로 렌더 스레드의 Gfx.WaitForCommands가 길다면 렌더 스레드가 새 렌더링 명령을 기다리는 상태이므로, 메인 스레드 작업이 늦어지고 있는지 확인합니다.

다만 Wait 마커 하나만으로 CPU 바운드나 GPU 바운드를 단정하기는 어렵습니다. Timeline에서 같은 시간대의 다른 스레드가 무엇을 하고 있는지 확인하고, 필요하면 GPU 모듈의 렌더링 시간까지 함께 보는 편이 적절합니다.


Timeline에서 확인하는 대기 패턴 작업 유휴 (대기) 메인 스레드 병목 후보 메인 스레드 렌더 스레드 → 렌더 스레드 대기: 메인 스레드 작업을 함께 확인 GPU 또는 Present 대기 후보 메인 스레드 렌더 스레드 → Present/렌더 대기: GPU 모듈과 VSync 설정을 함께 확인


게임 루프 (1)에서 다룬 PlayerLoop의 흐름은 Timeline 뷰에서 실제 실행 구간으로 확인할 수 있습니다. 스크립트 실행, 물리 시뮬레이션, 애니메이션, 렌더링 준비처럼 프레임을 구성하는 단계가 시간축 위에 나뉘어 나타나므로, 먼저 어느 구간이 프레임 시간을 크게 차지하는지 보고 그 안의 세부 항목으로 내려가면 병목 위치를 좁히기 쉽습니다.


GPU 모듈

CPU 모듈은 렌더링을 준비하는 CPU 작업까지 보여주지만, GPU가 실제로 렌더링을 처리하는 데 걸린 시간은 별도로 확인해야 합니다. GPU 모듈은 선택한 프레임에서 GPU 시간이 어떤 렌더링 단계에 쓰였는지를 보여줍니다.

GPU 모듈의 항목은 렌더 파이프라인과 그래픽스 설정에 따라 달라질 수 있습니다. 일반적으로는 불투명 오브젝트, 반투명 오브젝트, 그림자, 후처리처럼 렌더링 단계별 시간이 나뉘어 표시됩니다.

항목 의미 높게 나타날 때 확인할 부분
Opaque 불투명 오브젝트 렌더링 셰이더 복잡도, 조명 계산, 정점 수, 렌더링되는 오브젝트 수
Transparent 반투명 오브젝트 렌더링 오버드로우, 파티클 크기, 반투명 셰이더 비용
Shadows / Depth 그림자 맵 또는 깊이 관련 패스 그림자를 만드는 광원 수, Shadow Resolution, Shadow Distance
Post-processing 후처리 효과 전체 화면 효과, 샘플링 횟수, 렌더 타깃 해상도

GPU 시간은 단독 숫자보다 목표 프레임 시간과 함께 해석합니다. 60fps를 목표로 하면 한 프레임의 전체 예산은 약 16.67ms이고, 30fps를 목표로 하면 약 33.33ms입니다. GPU 시간이 이 예산에 가까워질수록 렌더링 쪽 병목 가능성이 커집니다.

다만 실제 프레임 병목은 CPU 시간과 GPU 시간을 함께 봐야 판단할 수 있습니다. GPU 시간이 높아도 CPU가 더 오래 걸리고 있다면, GPU 최적화만으로는 프레임 시간이 크게 줄지 않을 수 있습니다. 반대로 CPU 모듈에서 대기 마커가 길고 GPU 시간도 높은 프레임이라면, GPU 작업을 줄이는 쪽이 우선순위가 될 수 있습니다.


GPU 모듈은 GPU 시간의 분포를 보여주는 도구이지, 원인을 모두 직접 알려주는 도구는 아닙니다. 예를 들어 Transparent 시간이 높다면 반투명 패스가 무겁다는 사실은 알 수 있지만, 원인이 파티클 수인지, 화면을 크게 덮는 이펙트인지, 셰이더 비용인지까지는 추가 확인이 필요합니다. 또한 GPU 타이밍 데이터는 플랫폼과 그래픽스 API의 지원 여부에 따라 표시되지 않거나 값이 불안정할 수 있습니다.

원인을 좁힐 때는 도구마다 보는 범위를 분리합니다. GPU 모듈은 어떤 렌더링 단계가 무거운지 보여주고, Rendering 모듈은 Batches, SetPass Calls, Triangles, Vertices 같은 수량 지표를 보여줍니다. Frame Debugger는 실제로 실행된 드로우콜과 패스 순서를 확인할 때 사용합니다.

GPU 모듈의 데이터가 비어 있거나 값이 불안정하다면, GPU 타이밍을 지원하지 않는 환경일 수 있습니다. 이 경우에는 Rendering 통계나 플랫폼별 그래픽스 프로파일러를 함께 사용해 병목 후보를 좁힙니다.


Memory 모듈

CPU 모듈과 GPU 모듈이 한 프레임의 실행 시간을 보는 도구라면, Memory 모듈은 실행 중인 게임이 어떤 메모리를 얼마나 사용하는지 보여줍니다. 프레임 시간이 안정적이더라도 메모리 사용량이 계속 늘어나면 로딩 지연, GC 스파이크, 메모리 부족 같은 문제가 생길 수 있으므로 별도로 확인해야 합니다.

메모리 관리 (2) - 네이티브 메모리와 에셋에서 다룬 것처럼, Unity의 메모리는 C# 관리 힙과 C++ 네이티브 메모리로 나뉩니다. 텍스처, 메쉬, 오디오 같은 에셋은 주로 네이티브 메모리에 로드되고, 스크립트에서 생성한 관리 객체는 관리 힙에 올라갑니다. Memory 모듈은 이 사용량을 카테고리별로 나누어 보여주며, Simple 뷰와 Detailed 뷰에서 서로 다른 깊이로 확인할 수 있습니다.

Memory 모듈의 Simple 뷰 Total Used Memory 287 MB Total Reserved 342 MB ├── Unity (네이티브) 198 MB Textures 124 MB (156개) Meshes 18 MB (89개) Audio 12 MB (43개) Shaders 8 MB (27개) AnimationClips 6 MB (64개) Materials 3 MB (112개) Other 27 MB ├── Mono (관리 힙) 34 MB Used 22 MB Reserved 34 MB ├── GfxDriver 41 MB └── Other 14 MB


Simple 뷰에서는 전체 메모리 사용량과 카테고리별 비중을 빠르게 파악할 수 있습니다. 텍스처, 메쉬, 오디오처럼 큰 에셋 카테고리의 비중을 먼저 보면, 메모리 사용량이 어느 영역에 집중되어 있는지 대략적인 방향을 잡을 수 있습니다. 이후 Detailed 뷰에서 해당 카테고리의 개별 에셋을 확인하며 실제 원인을 좁혀갑니다.

Detailed 뷰와 스냅샷

Simple 뷰에서 메모리 사용이 큰 카테고리를 찾았다면, Detailed 뷰에서 더 세부적인 항목을 확인합니다. Take Sample 버튼을 누르면 현재 대상의 메모리 샘플을 수집하고, 메모리에 올라와 있는 Unity 오브젝트와 하위 카테고리를 목록 형태로 살펴볼 수 있습니다. 여기서는 어떤 텍스처, 메쉬, 머티리얼 같은 항목이 큰 비중을 차지하는지 확인하는 데 초점을 둡니다.

씬 전환 전후의 샘플을 비교하면, 해제되어야 할 에셋이 계속 남아 있는지 추적할 수 있습니다. 예를 들어 이전 씬에서만 사용하던 텍스처가 전환 후에도 남아 있다면, 전역 객체, 캐시, DontDestroyOnLoad 오브젝트, Addressables나 AssetBundle의 로드 상태처럼 참조가 유지되는 경로를 확인해야 합니다. 이 단계의 목적은 곧바로 메모리 누수로 단정하는 것이 아니라, 예상보다 오래 살아 있는 객체를 찾아내는 것입니다.


Memory Profiler 패키지

Unity Profiler의 Memory 모듈은 현재 메모리 사용량을 빠르게 확인하는 데 적합합니다. 하지만 시간이 지나면서 어떤 객체가 늘어났는지, 씬 전환 후에도 어떤 에셋이 남아 있는지, 특정 객체가 어떤 참조 때문에 해제되지 않는지까지 추적하려면 정보가 부족할 수 있습니다.

이런 분석이 필요할 때는 Package Manager에서 설치할 수 있는 Memory Profiler 패키지를 함께 사용하는 편이 적절합니다. Memory Profiler 패키지는 메모리 스냅샷을 저장하고, 두 스냅샷을 비교하고, 메모리를 차지하는 객체를 시각적으로 탐색하는 데 초점을 둔 도구입니다.


Memory Profiler의 주요 기능

기능 용도
Tree Map 메모리 사용량이 큰 카테고리와 객체를 시각적으로 파악
Snapshot 비교 두 시점 사이에 새로 생기거나 사라진 객체를 비교
References 예상보다 오래 남는 객체를 참조하는 경로 추적


Tree Map은 메모리 사용량이 큰 영역을 먼저 찾는 데 유용합니다. 텍스처 영역이 크게 보이면 해상도, 압축 포맷, 중복 로드 여부를 확인하고, 메쉬 영역이 크면 정점 수, 중복 메쉬, Read/Write Enabled 설정을 확인합니다. Read/Write Enabled는 런타임에 스크립트에서 메쉬 데이터를 읽거나 수정할 수 있게 하는 설정입니다. 이 옵션이 켜진 메쉬는 GPU에서 사용할 데이터 외에도 CPU에서 접근할 수 있는 복사본을 유지하므로, 필요하지 않은 경우 메모리 사용량을 늘릴 수 있습니다.

Snapshot 비교는 시간에 따른 메모리 변화를 볼 때 사용합니다. 씬 전환 전후의 스냅샷을 비교하면, 이전 씬에서 사용하던 객체가 사라졌는지 확인할 수 있습니다. 특정 플레이 구간을 반복할 때마다 같은 유형의 객체가 계속 늘어난다면, 의도적으로 캐시에 남긴 데이터인지, 해제되어야 하는데 참조가 유지되는 객체인지 구분해야 합니다.

References는 예상보다 오래 남는 객체의 참조 경로를 볼 때 사용합니다. 이전 씬의 텍스처나 머티리얼이 계속 남아 있다면, 어떤 오브젝트나 관리 객체가 그 에셋을 참조하는지 따라가며 원인을 좁힙니다. 전역 매니저, 캐시, DontDestroyOnLoad 오브젝트, Addressables나 AssetBundle의 로드 핸들처럼 수명이 긴 참조가 남아 있으면 에셋도 함께 유지될 수 있습니다.


Rendering 모듈과 Physics 모듈

CPU, GPU, Memory 모듈은 프레임 시간과 메모리 사용량을 큰 범위에서 파악하는 데 유용합니다. 병목 후보가 렌더링이나 물리 쪽으로 좁혀졌다면, 다음에는 해당 시스템의 수량 지표를 확인해야 합니다. Rendering 모듈과 Physics 모듈은 드로우콜, 삼각형 수, 물리 시뮬레이션 작업량처럼 원인을 더 구체적으로 좁히는 데 필요한 지표를 제공합니다.

Rendering 모듈

Rendering 모듈은 렌더링에 사용된 수량 지표를 보여줍니다. GPU 모듈이 렌더링 단계별 시간을 보여준다면, Rendering 모듈은 그 시간의 원인이 될 수 있는 Batches, SetPass Calls, Triangles, Vertices 같은 값을 보여줍니다.


Rendering 모듈 통계 예시

항목
SetPass Calls 42
Batches 156
Triangles 245,000
Vertices 382,000
Shadow Casters 12
Visible Skinned Meshes 8


Batches는 해당 프레임에서 렌더링 명령이 몇 개의 배치로 제출되었는지를 나타냅니다. 렌더 파이프라인 (2) - 드로우콜과 배칭에서 살펴본 것처럼, 같은 머티리얼과 렌더 상태를 공유하는 오브젝트는 배칭으로 묶일 수 있습니다. 배치 수가 많으면 CPU가 렌더링 명령을 준비하고 제출하는 비용이 커질 수 있습니다.

SetPass Calls는 셰이더 패스나 머티리얼 상태를 바꾸는 횟수에 가깝습니다. 배치 수가 많더라도 SetPass Calls가 낮다면 같은 상태를 유지한 채 여러 배치가 연속으로 처리되고 있을 가능성이 있습니다. 반대로 SetPass Calls가 높다면 머티리얼, 셰이더, 키워드, 렌더 패스가 자주 바뀌고 있는지 확인합니다.

TrianglesVertices는 해당 프레임에서 렌더링 대상이 된 지오메트리의 양을 보여줍니다. 이 값은 카메라 거리, LOD 구성, 스키닝 대상 수, 그림자 패스 여부에 따라 달라집니다. GPU 모듈에서 Opaque나 Shadows 시간이 높게 나타난다면, Rendering 모듈의 삼각형 수와 정점 수를 함께 보며 지오메트리 부담이 원인인지 확인합니다.

Game View의 Stats 창에서도 Batches, SetPass Calls, Triangles 같은 렌더링 통계를 빠르게 볼 수 있습니다. 특히 Saved by batching 값은 배칭으로 줄어든 배치 수를 확인할 때 참고할 수 있습니다. 다만 이 값이 낮다고 해서 항상 문제가 있는 것은 아닙니다. 장면에 배칭할 대상이 적을 수도 있으므로, Batches와 SetPass Calls가 실제로 높은지 먼저 보고, 필요할 때 렌더 파이프라인 (2) - 드로우콜과 배칭에서 다룬 배칭 조건을 함께 점검합니다.

Physics 모듈

Physics 모듈은 물리 엔진이 처리한 Rigidbody, Collider, Contact, Constraint의 규모를 보여줍니다. CPU 모듈의 FixedUpdate 영역에서 물리 시뮬레이션 시간이 길게 나타났다면, Physics 모듈에서 어떤 물리 지표가 함께 증가했는지 확인합니다.

대표적인 지표는 Dynamic Bodies, Active Dynamic Bodies, Static Colliders, Contacts, Trigger Overlaps, Active Constraints입니다. 이 값들은 Hierarchy에 있는 GameObject 수와 1:1로 대응하지 않습니다. 하나의 GameObject에 여러 Collider가 붙어 있을 수도 있고, 잠든 Rigidbody처럼 씬에는 존재하지만 해당 프레임에 거의 처리되지 않는 컴포넌트도 있을 수 있습니다.

해석의 기준은 수치가 어떤 물리 작업량을 가리키는지입니다. Active Dynamic Bodies가 많다면 위치와 속도를 갱신해야 하는 Rigidbody가 많다는 의미이고, Contacts나 Trigger Overlaps가 많다면 Collider 쌍 사이의 접촉이나 트리거 겹침이 많이 발생한다는 의미입니다. Active Constraints가 높다면 Joint나 충돌 반응처럼 Solver가 맞춰야 할 제약이 많다는 신호로 볼 수 있습니다.

이 수치들은 바로 해결책을 말해주기보다 조사 방향을 정하는 데 쓰입니다. Active Dynamic Bodies가 높다면 불필요하게 깨어 있는 Rigidbody나 멀리 있는 물리 오브젝트를 확인하고, Contacts가 높다면 콜라이더 형태, 레이어 충돌 조합, 접촉이 과도하게 발생하는 배치를 살펴봅니다. 복잡한 메시 콜라이더가 많은 장면이라면 단순한 콜라이더 조합으로 대체할 수 있는지도 함께 검토합니다.


Frame Debugger

Profiler가 프레임 시간과 렌더링 수량을 보여준다면, Frame Debugger는 한 프레임이 어떤 드로우콜과 렌더링 패스로 구성되는지 단계별로 보여줍니다. 렌더링 결과가 어떤 순서로 만들어졌는지, 어느 드로우콜에서 상태가 바뀌었는지 확인할 때 사용합니다. Unity에서는 Window → Analysis → Frame Debugger에서 열 수 있습니다.

드로우콜 재생

Frame Debugger를 활성화하면 현재 프레임이 정지하고, 왼쪽 패널에 렌더링 이벤트 목록이 표시됩니다. 각 항목을 선택하면 그 시점까지 실행된 결과가 Game View에 표시되므로, 한 프레임이 어떤 순서로 만들어지는지 단계별로 따라갈 수 있습니다.

이 과정에서 그림자 패스가 언제 실행되는지, 불투명 오브젝트와 반투명 오브젝트가 어떤 순서로 그려지는지, 후처리 패스가 어느 단계에서 적용되는지 확인할 수 있습니다. 특정 오브젝트가 예상보다 여러 번 그려지거나, 불필요한 패스가 추가되는 경우도 이 뷰에서 찾기 쉽습니다.

배칭 깨짐 원인 확인

Frame Debugger는 배칭 결과를 실제 드로우콜 단위로 확인할 때 유용합니다. Stats 창이나 Rendering 모듈에서 배치 수가 높게 나타났다면, Frame Debugger에서 어떤 오브젝트가 같은 배치로 묶였고 어떤 오브젝트가 별도의 드로우콜로 분리되었는지 따라갈 수 있습니다.

렌더 파이프라인 (2) - 드로우콜과 배칭에서 다룬 것처럼, 오브젝트가 같은 배치로 묶이려면 머티리얼, 셰이더 키워드, 렌더 상태 등이 서로 맞아야 합니다. 실제 장면에서는 라이트맵, 그림자, 렌더 패스 차이 때문에 비슷해 보이는 오브젝트도 별도의 드로우콜로 분리될 수 있습니다.

Frame Debugger에서는 이런 분리가 어느 지점에서 발생했는지 드로우콜 순서와 상태 정보를 따라가며 확인할 수 있습니다. 배칭이 기대만큼 동작하지 않을 때는 이 뷰로 먼저 분리되는 구간을 찾는 편이 적절합니다.

원인을 확인한 뒤에는 배칭 조건과 렌더링 상태 변경을 줄이는 최적화를 검토할 수 있습니다. 배칭 최적화에서 배치 수 감소는 결과 지표에 가깝습니다. 실제로 확인해야 할 것은 Batches나 SetPass Calls가 높아서 CPU의 렌더링 준비 시간이 늘어나고 있는지입니다. CPU 모듈에서도 렌더링 준비 구간이 문제로 보인다면, 그때 배칭 조건과 렌더링 상태 변경을 줄이는 최적화를 적용하는 편이 적절합니다.

머티리얼 통합, 텍스처 아틀라스, Static Batching, SRP Batcher, GPU Instancing 같은 배칭 최적화는 렌더 파이프라인 (2) - 드로우콜과 배칭에서 더 자세히 다룹니다.

오버드로우 시각화

오버드로우(Overdraw) 뷰는 Scene View의 Draw Mode 중 하나입니다. 이 모드로 전환하면 오브젝트가 겹쳐 그려지는 영역이 더 밝게 표시되므로, 같은 화면 영역이 여러 번 렌더링되는 위치를 빠르게 찾을 수 있습니다.

Frame Debugger로 어떤 드로우콜이 실행되었는지 확인한 뒤, Overdraw 뷰로 그 드로우콜들이 화면 어디에서 겹치는지 확인하면 오버드로우 원인을 더 좁히기 쉽습니다.

GPU 모듈에서 Transparent나 UI 관련 시간이 높게 나타났다면, Overdraw 뷰로 실제로 겹침이 집중되는 화면 영역을 확인합니다. 밝게 표시되는 영역이 특정 파티클 이펙트나 UI 그룹에 몰려 있다면, 그 요소의 크기, 겹침 수, 표시 조건을 우선적으로 점검합니다.


프로파일러 데이터 읽기 — 실전 흐름

지금까지 살펴본 모듈은 성능 문제를 서로 다른 관점에서 보여줍니다. CPU 모듈은 어떤 코드와 엔진 작업이 시간을 쓰는지, GPU 모듈은 어떤 렌더링 단계가 무거운지, Memory 모듈은 어떤 데이터가 메모리를 차지하는지 보여줍니다. 실제 분석에서는 이 정보를 따로 보지 않고, 프레임 시간에서 시작해 원인 후보를 좁혀가는 순서로 연결합니다.

프로파일링 진단 흐름 1. 프레임 시간과 스파이크 확인 CPU 모듈과 Timeline에서 긴 프레임 선택 평균 시간, 최대 시간, 반복 구간을 함께 확인 2. 병목 후보 영역 좁히기 CPU 시간, GPU 시간, 메모리 변화를 비교 Wait 마커는 확정이 아니라 단서로 해석 CPU 후보 Hierarchy: Self 시간 Timeline: 대기 구간 GC Alloc: 반복 할당 GPU / 렌더링 후보 GPU 모듈: 단계별 시간 Rendering: 수량 지표 Frame Debugger, Overdraw Memory 후보 Memory 모듈: 카테고리 Snapshot 비교 References: 참조 경로 4. 수정 후 같은 조건에서 재측정 개선 여부와 부작용 확인, 필요하면 다시 후보를 좁힘


분석은 문제가 나타나는 프레임이나 구간을 고르는 것에서 시작합니다. 평균 프레임 시간만 보면 짧은 스파이크가 묻힐 수 있으므로, 반복적으로 튀는 프레임과 특정 플레이 상황에서만 길어지는 프레임을 함께 봅니다. 목표 프레임 시간을 넘는 프레임이 있다면 그 프레임을 선택하고, 메모리 증가가 문제라면 같은 구간의 메모리 샘플을 함께 남깁니다.

그다음에는 원인 후보를 큰 범위에서 나눕니다. CPU 모듈의 긴 함수와 Timeline의 Wait 마커는 CPU 쪽 문제를 좁히는 단서가 되고, GPU 모듈의 렌더링 시간과 Rendering 모듈의 수량 지표는 렌더링 쪽 문제를 좁히는 단서가 됩니다. 메모리 사용량이 계속 증가한다면 Memory 모듈과 Snapshot 비교로 오래 남는 객체를 추적합니다.

이 단계에서는 한 가지 수치만 보고 결론을 내리지 않는 편이 적절합니다. Profiler의 각 모듈은 같은 프레임을 다른 관점에서 보여주기 때문에, 하나의 수치가 높게 나타난 이유도 여러 가지일 수 있습니다. 예를 들어 GPU 시간이 높다면 셰이더 비용, 오버드로우, 그림자, 해상도 같은 여러 원인이 가능하므로, Rendering 모듈의 수량 지표와 Frame Debugger의 드로우콜 흐름을 함께 확인해야 원인을 좁힐 수 있습니다.

후보 영역이 좁혀지면 세부 원인으로 내려갑니다. CPU 쪽은 Self 시간이 높은 함수와 반복 할당을 찾고, GPU 쪽은 무거운 렌더링 단계가 어떤 드로우콜과 화면 영역에서 발생하는지 확인합니다. Memory 쪽은 증가한 객체가 어떤 시점에 생겼고 어떤 참조 때문에 남아 있는지 추적합니다.

최적화는 적용한 뒤 다시 측정해야 의미가 있습니다. 같은 장면과 같은 조건에서 다시 측정해 프레임 시간, 스파이크 빈도, 메모리 사용량이 실제로 줄었는지 비교합니다. 변화가 없다면 처음 추정한 원인이 빗나갔거나, 더 큰 병목이 다른 곳에 남아 있을 수 있습니다. 이 경우에는 다시 프레임을 선택하고 후보 영역을 좁히는 과정으로 돌아갑니다.


프로파일러에서 보이는 병목 패턴

프로파일러를 계속 보다 보면 자주 반복되는 병목 양상이 있습니다. GC 스파이크, 높은 SetPass, 오버드로우, 물리 접점 증가처럼 특정 수치와 마커가 함께 나타나는 경우입니다. 이런 패턴은 앞선 글에서 다룬 최적화 원리와 연결되어 있으므로, 원인 후보를 빠르게 좁히는 데 도움이 됩니다.

GC 스파이크

CPU 모듈에서 특정 프레임의 시간이 갑자기 늘고, 그 프레임에 GC.Collect 같은 GC 관련 마커가 길게 나타나는 패턴입니다. 보통 이전 프레임들에서 관리 힙 할당이 반복적으로 쌓이다가, 어느 시점에 가비지 컬렉션이 실행되면서 프레임 시간이 튀는 형태로 나타납니다.

GC가 실행된 프레임은 결과가 나타난 시점에 가깝습니다. 원인은 그 이전 프레임들에서 반복적으로 발생한 힙 할당일 수 있으므로, GC가 발생한 프레임만 보기보다 주변 프레임의 GC Alloc 컬럼까지 함께 보는 편이 적절합니다. Update()처럼 매 프레임 실행되는 경로에서 문자열, 배열, 임시 객체가 계속 생성되고 있다면 GC 스파이크의 원인 후보가 됩니다. 이런 경우에는 오브젝트 풀링, 캐싱, 재사용 가능한 버퍼로 반복 할당을 줄이는 방향을 검토합니다.

관리 힙과 GC가 프레임 스파이크로 이어지는 과정은 메모리 관리 (1) - 가비지 컬렉션의 원리에서 더 자세히 다룹니다.

높은 SetPass

Rendering 모듈에서 SetPass Calls가 Batches에 가깝게 나타나는 패턴입니다. SetPass Calls는 셰이더 패스나 머티리얼 상태가 바뀌는 횟수에 가깝기 때문에, 이 값이 높으면 CPU가 렌더링 상태를 자주 바꾸고 있을 가능성이 있습니다.

높은 SetPass 패턴

Batches: 280, SetPass Calls: 245 → 대부분의 배치에서 렌더 상태 변경 발생

이 패턴은 머티리얼이나 셰이더 키워드가 자주 바뀌거나, 렌더 패스가 나뉘거나, 배칭 조건이 맞지 않을 때 나타날 수 있습니다. 먼저 CPU 모듈에서 렌더링 준비 시간이 실제로 높은지 확인하고, Frame Debugger에서 어떤 지점에서 드로우콜이 분리되는지 따라가는 편이 적절합니다.

배칭 조건과 대응 방법은 렌더 파이프라인 (2) - 드로우콜과 배칭에서 더 자세히 다룹니다.

오버드로우 과다

GPU 모듈에서 Transparent 계열 패스나 UI 렌더링 시간이 높고, Scene View의 Overdraw 뷰에서 같은 화면 영역이 밝게 누적되어 보이는 패턴입니다. 이 조합은 GPU 시간이 지오메트리 처리보다 픽셀을 반복해서 그리는 과정에서 늘어났을 가능성을 보여줍니다.

오버드로우 과다 패턴

  • GPU 모듈: Transparent 계열 패스 또는 UI 렌더링 시간이 높음
  • Overdraw 뷰: 파티클, UI, 반투명 이펙트가 겹치는 영역이 밝게 표시됨

밝게 누적되는 영역이 파티클 이펙트에 몰려 있다면 파티클의 화면상 크기, 개수, 수명, 중첩 범위가 우선 확인 대상입니다. UI에서 넓은 영역이 밝게 나타난다면 보이지 않는 UI 요소가 활성 상태로 남아 있는지, 반투명 패널이 여러 겹 쌓여 있는지 확인합니다.

대응 방향은 같은 픽셀을 반복해서 그리는 면적과 횟수를 줄이는 쪽으로 잡습니다. 파티클 크기나 동시 표시 수를 줄이고, 필요 없는 UI 그룹을 비활성화하며, 반투명 레이어가 불필요하게 겹치지 않도록 정리하는 방식입니다.

오버드로우가 파티클 비용으로 이어지는 원리와 파티클 최적화 기준은 파티클과 애니메이션 (1)에서 더 자세히 다룹니다.

물리 연산 과다

CPU 모듈에서 Physics.Simulate 같은 물리 관련 마커가 길게 나타나거나, FixedUpdate 구간에서 물리 처리 시간이 크게 보이는 패턴입니다. 다만 FixedUpdate에는 사용자 스크립트도 함께 포함될 수 있으므로, Physics 모듈의 수치와 함께 보며 물리 엔진 쪽 비용인지 구분하는 편이 적절합니다.

물리 연산 과다 패턴

  • CPU 모듈: Physics.Simulate 같은 물리 관련 마커가 길게 나타남
  • Physics 모듈: Active Dynamic Bodies, Contacts, Active Constraints 등이 함께 증가

Physics 모듈에서는 어떤 물리 요소가 함께 증가했는지 먼저 봅니다. Active Dynamic Bodies가 높다면 움직이거나 깨어 있는 Rigidbody가 많다는 뜻이고, Contacts나 Trigger Overlaps가 높다면 충돌이나 트리거 겹침을 많이 처리하고 있다는 의미입니다. Active Constraints가 높다면 Joint나 충돌 반응처럼 Solver가 처리해야 할 제약이 많은 상황일 수 있습니다.

대응 방향은 증가한 수치에 맞춰 달라집니다. 활성 Rigidbody가 많다면 멀리 있거나 필요 없는 물리 오브젝트를 비활성화하는 방향을 검토하고, Contacts가 많다면 콜라이더 형태와 레이어 충돌 조합을 확인합니다. 복잡한 MeshCollider가 접촉 비용을 키우고 있다면 BoxCollider나 SphereCollider 같은 단순한 Primitive 콜라이더 조합으로 대체하는 것도 후보가 됩니다.

물리 시뮬레이션 단계와 Collider 비용은 물리 최적화 (1) - 물리 엔진의 실행 구조에서, Layer Collision Matrix를 포함한 충돌 검사 범위 조정은 물리 최적화 (2) - 물리 최적화 전략에서 더 자세히 다룹니다.

렌더링 시간 과다

CPU 모듈에서 Camera.Render 아래의 렌더링 준비 구간이 길게 나타나는 패턴입니다. 이름에 Render가 들어 있어 GPU 시간처럼 보일 수 있지만, CPU 모듈에 표시되는 이 구간은 CPU가 렌더링 명령을 준비하는 시간입니다. 카메라에 보이는 오브젝트를 고르는 컬링, 렌더링 순서 정렬, 드로우콜 제출 같은 작업이 여기에 포함됩니다.

렌더링 CPU 비용 과다 패턴

  • CPU 모듈: Camera.Render 하위 구간이 길게 나타남
  • Rendering 모듈: Batches, SetPass Calls, Visible Skinned Meshes 같은 수치가 함께 높음

이 패턴이 보이면 먼저 어떤 하위 구간이 긴지 나눠 봅니다. 컬링 관련 구간이 길다면 카메라가 검사해야 할 Renderer 수가 많다는 신호일 수 있고, Opaque나 Transparent 렌더링 구간이 길다면 컬링을 통과한 오브젝트의 정렬과 렌더링 명령 제출 비용이 커진 상황일 수 있습니다. Skinned Mesh가 많은 장면에서는 보이는 캐릭터 수와 스키닝 대상도 함께 확인합니다.

대응 방향은 CPU가 렌더링 대상으로 처리해야 하는 오브젝트 수와 상태 변경 횟수를 줄이는 쪽입니다. 오클루전 컬링으로 가려진 오브젝트를 제외하고, 거리나 중요도 기준으로 멀리 있는 오브젝트를 비활성화하며, LOD로 먼 오브젝트의 지오메트리와 렌더러 부담을 낮추는 방식입니다. Batches나 SetPass Calls가 함께 높다면 Frame Debugger로 드로우콜 분리 원인을 확인합니다.

카메라 컬링과 렌더링 단계의 흐름은 렌더 파이프라인 (1) - 카메라에서 화면까지에서, 드로우콜과 배칭 조건은 렌더 파이프라인 (2) - 드로우콜과 배칭에서 더 자세히 다룹니다.


이런 패턴은 병목이 CPU 스크립트, 렌더링 준비, GPU 렌더링, 물리, 메모리 중 어느 쪽에 가까운지 좁히는 데 도움이 됩니다. 그중 CPU 스크립트 쪽 함수가 원인으로 보인다면, 다음 단계에서는 그 함수 내부의 어느 코드가 시간을 쓰는지 더 자세히 봐야 합니다.


Deep Profiling

기본 프로파일링으로는 어떤 MonoBehaviour 콜백이나 Unity 엔진 구간이 오래 걸렸는지 확인할 수 있습니다. 하지만 그 안에서 호출된 사용자 C# 함수들의 비용은 하나의 상위 항목에 합쳐져 보이는 경우가 많습니다. 예를 들어 EnemyManager.Update()가 5ms로 보이더라도, 그 안의 타겟 탐색, 거리 계산, UI 갱신 중 어느 부분이 큰지 바로 알기는 어렵습니다.

이처럼 병목이 특정 콜백이나 함수까지 좁혀졌지만 내부 호출 구조를 더 봐야 할 때 Deep Profiling을 사용합니다. Deep Profiling은 사용자 C# 함수 호출까지 더 세밀하게 펼쳐 보여주므로, 비용이 실제로 쌓이는 지점을 찾는 데 도움이 됩니다.


기본 프로파일링 vs Deep Profiling 기본 프로파일링 ▼ EnemyManager.Update() 5.1 ms 사용자 함수 비용이 상위 항목에 합산되어 보일 수 있음 ProcessAllEnemies(), UpdateUI() 등 내부 호출은 세분화되지 않음 Deep Profiling ▼ EnemyManager.Update() 예시 ▼ ProcessAllEnemies() 큰 비중 ▼ FindNearestTarget() 높음 CalculateDistance() 중간 CompareDistances() 높음 ApplyBehavior() 중간 UpdateUI() 낮음

Deep Profiling은 사용자 스크립트의 함수 호출까지 더 촘촘하게 계측하므로, 일반 프로파일링보다 오버헤드가 큽니다. 이 상태의 프레임 시간은 실제 실행 시간보다 부풀려질 수 있어, 절대적인 ms 값을 그대로 성능 기준으로 삼기에는 적절하지 않습니다.

따라서 Deep Profiling은 최종 성능을 판단하는 용도보다, 특정 콜백 내부에서 비용이 어디로 흘러가는지 확인하는 용도에 가깝습니다. 예를 들어 Update() 전체가 비싸게 보일 때, 그 안의 타겟 탐색, 거리 계산, UI 갱신 중 어떤 호출이 상대적으로 큰 비중을 차지하는지 파악하는 식입니다.

반복해서 측정해야 하는 코드라면 Deep Profiling을 계속 켜 두기보다, 관심 있는 코드 블록에 직접 프로파일링 마커를 넣는 방식이 더 실용적입니다. Unity의 ProfilerMarker를 사용하면 원하는 구간만 Hierarchy 뷰에 별도 항목으로 표시할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using Unity.Profiling;

static readonly ProfilerMarker FindTargetMarker =
    new ProfilerMarker("ProcessEnemies.FindTarget");
static readonly ProfilerMarker ApplyAIMarker =
    new ProfilerMarker("ProcessEnemies.ApplyAI");

void ProcessEnemies() {
    using (FindTargetMarker.Auto()) {
        FindNearestTarget();
    }

    using (ApplyAIMarker.Auto()) {
        ApplyBehavior();
    }
}

이렇게 마커를 넣으면 프로파일러 Hierarchy에 ProcessEnemies.FindTargetProcessEnemies.ApplyAI가 별도 항목으로 표시됩니다. Deep Profiling처럼 모든 내부 호출을 펼치지는 않지만, 의심되는 구간을 반복 측정할 때는 오버헤드가 작고 결과를 해석하기도 쉽습니다.


프로파일링 시 주의 사항

프로파일러의 수치는 측정 환경의 영향을 크게 받습니다. 같은 장면이라도 에디터에서 재생하는지, 빌드에서 실행하는지, VSync나 디버깅 옵션이 켜져 있는지에 따라 프레임 시간이 다르게 보일 수 있습니다. 따라서 병목을 판단하기 전에 측정 조건부터 확인하는 편이 적절합니다.

에디터 오버헤드

에디터에서 Play 모드로 프로파일링하면 게임 코드뿐 아니라 에디터가 수행하는 작업도 함께 측정됩니다. Inspector 갱신, Scene View 렌더링, 에디터 확장 코드, OnValidate 호출 같은 작업이 CPU 시간에 섞일 수 있습니다.

그래서 에디터 프로파일링은 병목 후보를 빠르게 찾는 용도로는 유용하지만, 최종 성능 판단 기준으로 삼기에는 부족합니다. 실제 프레임 시간과 병목 여부는 빌드에서 다시 측정해 확인하는 편이 적절합니다.

스크립트 디버깅 오버헤드

에디터가 아닌 실행 빌드의 성능을 보려면, 빌드가 프로파일러에 연결될 수 있어야 합니다. Unity에서는 이를 위해 Development Build를 사용합니다. 이 설정을 켜면 프로파일러 연결에 필요한 정보가 빌드에 포함되고, 필요하다면 Autoconnect Profiler로 실행 시점에 자동 연결할 수 있습니다.

Build Settings에는 Development Build와 함께 Script Debugging 옵션도 표시됩니다. 이름 때문에 프로파일링에 함께 필요한 설정처럼 보일 수 있지만, 목적은 다릅니다. Script Debugging은 성능 측정이 아니라, 브레이크포인트를 걸고 C# 스크립트 실행을 디버거로 따라가기 위한 설정입니다. 성능을 측정할 때까지 켜 둘 필요는 없으며, 켜져 있으면 디버깅을 위한 추가 비용이 프레임 시간에 섞일 수 있습니다.

정리하면, 빌드에서 성능을 측정할 때는 Development Build로 프로파일러 연결을 준비하고, Script Debugging은 스크립트 디버깅이 필요한 경우에만 켜는 편이 적절합니다.

VSync와 프레임 레이트 제한

프로파일러에서 프레임 시간이 16.67ms, 33.33ms처럼 목표 프레임 시간 근처에 일정하게 고정되어 보인다면, 작업 시간이 아니라 대기 시간이 포함되어 있을 수 있습니다. 대표적인 원인은 VSync(디스플레이 갱신 주기에 프레임 제출을 맞추는 설정)와 Application.targetFrameRate입니다.

이런 설정이 켜져 있으면 CPU와 GPU 작업이 일찍 끝나도 다음 프레임을 시작하거나 화면에 표시할 시점까지 기다립니다. 이 대기 시간은 Timeline 뷰에서 WaitForTargetFPS, Gfx.WaitForPresentOnGfxThread 같은 마커로 보일 수 있습니다. 따라서 프레임 시간이 목표값에 맞춰 평평하게 보일 때는, 먼저 실제 작업 시간이 긴 것인지 프레임 제한 때문에 기다리는 것인지 구분해야 합니다.

GPU 병목을 확인할 때는 VSync나 프레임 제한을 잠시 끄고 비교하거나, Timeline 뷰에서 대기 마커를 제외한 실제 CPU/GPU 작업 구간을 함께 보는 편이 적절합니다. 이렇게 해야 렌더링 자체가 느린 것인지, 단순히 프레임 제한에 맞춰 기다리는 것인지 구분할 수 있습니다.

프레임 평균 vs 스파이크

평균 프레임 시간이 목표 예산 안에 들어와도, 간헐적으로 긴 프레임이 섞이면 플레이 중에는 끊김으로 느껴질 수 있습니다. 예를 들어 대부분의 프레임이 12~14ms에 머물러도, 몇 초마다 한 번씩 40ms 이상의 프레임이 발생하면 평균값만으로는 문제를 확인하기 어렵습니다.

따라서 프로파일러에서는 평균 프레임 시간과 별도로, 그래프에서 위로 튀는 스파이크 프레임을 분석 대상으로 잡는 편이 적절합니다. 스파이크가 발생한 프레임의 CPU Hierarchy, Timeline, GC Alloc, Memory 변화, 로딩 마커를 함께 보면 원인을 좁히기 쉽습니다.

지속적으로 프레임 시간이 높은 경우와 특정 순간에만 튀는 경우는 원인이 다를 수 있습니다. 전자는 매 프레임 반복되는 작업량이 많은 상황이고, 후자는 GC, 에셋 로딩, 씬 전환, 물리 접촉 증가, 동기식 파일 I/O처럼 특정 시점에 몰리는 작업이 원인일 수 있습니다. 따라서 평균값만으로 판단하기보다, 긴 프레임이 언제 발생했고 그 프레임에서 어떤 마커가 함께 나타났는지 확인하는 흐름이 필요합니다.


마무리

프로파일링의 목적은 단순히 숫자를 보는 것이 아니라, 프레임 안에서 시간이 어디에 쓰이는지 설명할 수 있는 상태를 만드는 것입니다. CPU 모듈의 함수 시간, Timeline의 대기 구간, GPU 모듈의 렌더링 시간, Rendering 모듈의 수량 지표, Memory 모듈의 사용량은 서로 다른 관점에서 같은 프레임을 보여줍니다.

따라서 하나의 수치만 보고 바로 최적화를 적용하기보다, 여러 모듈의 신호를 맞춰 보며 원인 후보를 좁히는 흐름이 중요합니다. 병목 후보를 찾은 뒤에는 같은 장면과 같은 조건에서 다시 측정하여 실제로 프레임 시간, 스파이크 빈도, 메모리 사용량이 개선되었는지 확인해야 합니다.

  • CPU 비용은 Hierarchy의 Total/Self 시간과 Timeline의 스레드 흐름으로 좁힙니다.
  • GPU 비용은 GPU 모듈의 단계별 시간, Rendering 모듈의 Batches·SetPass·Triangles, Frame Debugger를 함께 봅니다.
  • 메모리 문제는 Memory 모듈과 Memory Profiler의 Snapshot 비교, References 정보로 추적합니다.
  • GC 스파이크, 높은 SetPass, 오버드로우, 물리 비용, 렌더링 CPU 비용 같은 반복 패턴을 기준으로 원인 후보를 빠르게 좁힙니다.
  • 함수 내부까지 봐야 할 때는 Deep Profiling을 사용하되, 반복 측정이 필요한 구간에는 ProfilerMarker를 넣어 확인합니다.
  • 빌드 측정에서는 Development Build, Script Debugging, VSync, 프레임 제한처럼 수치를 바꿀 수 있는 조건을 먼저 확인합니다.


Unity Profiler와 Frame Debugger는 병목의 위치를 찾는 출발점으로 충분히 유용하지만, 모든 정보를 보여주는 도구는 아닙니다. 빌드 환경에서 안정적으로 측정하는 방법, 그리고 그래픽스 API 호출 흐름이나 셰이더 실행 비용처럼 더 낮은 수준의 정보를 확인하는 방법은 별도로 다뤄야 합니다.

Part 2에서는 빌드 프로파일링 환경을 구성하고, Unity Profiler 밖의 보조 도구를 함께 사용하는 방법을 이어서 다룹니다.



관련 글

시리즈

전체 시리즈

Tags: Profiler, Unity, 최적화, 프로파일링

Categories: ,