08/21 Unity Profiler - 메모리 최적화, Draw Call 최적화, Object Pooling

심지태
∙∙조회 14
08/21 Unity Profiler - 메모리 최적화, Draw Call 최적화, Object Pooling

Profiler에서 확인할 3가지 핵심 지표

  • CPU Spike
    특정 프레임에서 CPU 사용량이 갑자기 증가하는 현상입니다. Profiler의 CPU Usage를 통해 어떤 함수가 프레임을 지연시키는지 확인합니다.
  • GC Alloc
    실행 중 새로운 관리 메모리가 얼마나 할당되는지 보여줍니다. 매 프레임 불필요한 메모리 할당이 반복되면 이후 Garbage Collection이 발생하면서 순간적인 프레임 드롭으로 이어질 수 있습니다.
  • SetPass Calls
    렌더링 과정에서 GPU에 렌더링 상태와 관련된 명령을 보내는 횟수를 확인하는 지표입니다. Material이나 렌더링 상태가 지나치게 다양하면 값이 증가할 수 있으므로 Material 정리, Batching, GPU Instancing 등을 검토합니다.

이번 단원 핵심 용어

용어핵심 의미
Profiler실행 중 게임의 CPU, GPU, Memory, Rendering 등의 성능을 측정하는 Unity 도구
GC AllocationC# 관리 힙에 새로운 메모리가 할당되어 이후 Garbage Collection 대상이 되는 현상
Draw Call / BatchingGPU에 보내는 렌더링 명령과 여러 렌더링 작업을 묶어 호출 횟수를 줄이는 방식
Static Batching움직이지 않는 오브젝트를 묶어 Draw Call을 줄이는 방법
Dynamic Batching조건을 만족하는 움직이는 오브젝트를 런타임에 묶어 렌더링하는 방법
GPU Instancing동일한 Mesh·Material을 사용하는 다수의 오브젝트를 효율적으로 한꺼번에 렌더링하는 방법
LOD Group카메라와의 거리에 따라 더 단순한 Mesh로 자동 전환하여 렌더링 비용을 줄이는 Component
Object Pooling오브젝트를 반복 생성·삭제하지 않고 미리 생성한 뒤 재사용하는 패턴
감지 주기(Tick Rate)AI가 주변 상황을 감지하고 판단하는 실행 간격. 매 프레임 실행할 필요는 없음

Unity Profiler

최적화를 할 때, 프로파일러에서는 대표적으로 다음 3가지를 본다.

  1. CPU
  2. Rendering(GPU)
  3. Memory

상단 그래프 영역

Profiler의 상단 Module Chart 영역은 게임을 실행하면서 성능 지표가 시간에 따라 어떻게 변하는지 전체적인 흐름을 관찰하는 곳입니다. 가로축은 프레임의 진행을 나타내며, 그래프가 갑자기 위로 튀는 지점을 선택하면 해당 프레임을 하단 Details Pane에서 자세히 분석할 수 있습니다. 따라서 상단에서는 “언제 문제가 발생했는가?”를 찾고, 하단에서는 “왜 문제가 발생했는가?”를 찾는 구조라고 이해하면 좋습니다.

Module확인하는 내용주요 활용
CPU Usage한 프레임을 처리하는 데 걸린 CPU 시간CPU Spike, 느린 프레임 탐색
RenderingSetPass Calls, Batches, Triangles, Vertices 등Draw Call 및 렌더링 부하 확인
Memory게임이 사용 중인 메모리 변화메모리 증가, 할당 문제 확인

CPU Usage

항목설명
Rendering카메라 렌더링, 드로우콜, 오브젝트 표시 등 화면을 그리는 데 사용되는 CPU 작업입니다.
ScriptsUpdate(), Coroutine, 사용자 작성 C# 코드 등 스크립트 실행에 사용되는 CPU 작업입니다.
Physics충돌 검사, Rigidbody, Collider 등 물리 연산에 사용되는 CPU 작업입니다.
AnimationAnimator, Animation Clip, 본 계산 등 애니메이션 처리에 사용되는 CPU 작업입니다.
GarbageCollector사용하지 않는 Heap 메모리를 탐색하고 해제하는 GC 작업에 사용되는 CPU 시간입니다. 값이 순간적으로 크게 증가하면 GC 스파이크를 의심할 수 있습니다.
VSync모니터의 화면 갱신 주기에 맞추기 위해 CPU가 대기하는 시간입니다.
Global Illumination간접광, 라이트맵 등 GI(Global Illumination) 관련 연산에 사용되는 시간입니다.
UICanvas 갱신, UI 레이아웃 및 그래픽 처리 등 Unity UI 처리에 사용되는 CPU 작업입니다.
Others위 항목으로 분류되지 않은 기타 엔진 작업 및 CPU 사용량입니다.

Rendering

항목설명
Batches Count한 프레임에서 GPU에 전달되는 렌더링 작업 묶음(Batch)의 개수입니다. 값이 많을수록 CPU의 렌더링 명령 처리 부담이 커질 수 있습니다.
SetPass Calls Count셰이더나 머티리얼 등 렌더링 상태를 변경하기 위해 발생한 호출 횟수입니다. 일반적으로 적을수록 렌더링 효율이 좋습니다.
Triangles Count현재 프레임에서 렌더링되는 삼각형의 총 개수입니다. 값이 지나치게 높으면 GPU 부하가 증가할 수 있습니다.
Vertices Count현재 프레임에서 처리되는 정점(Vertex)의 총 개수입니다. 값이 높을수록 메시 처리 비용이 증가할 수 있습니다.

Memory

항목설명
Total Used Memory현재 Unity 애플리케이션이 사용하고 있는 전체 메모리 사용량입니다. 텍스처, 메시, 오디오, 스크립트 객체 등 여러 메모리 사용량이 포함됩니다.
Texture MemoryTexture, Sprite, RenderTexture 등 텍스처 리소스가 차지하는 메모리입니다. 고해상도 텍스처가 많을수록 크게 증가할 수 있습니다.
Mesh Memory캐릭터나 맵 등의 Mesh 데이터가 차지하는 메모리입니다. 정점이나 삼각형 수가 많은 메시를 많이 사용할수록 증가합니다.
Material Count현재 메모리에 존재하는 Material의 개수입니다. 불필요하게 Material 인스턴스를 생성하면 개수가 계속 늘어날 수 있습니다.
Object Count현재 메모리에 존재하는 Unity Object의 개수입니다. GameObject, Component, Texture, Material 등의 객체가 포함됩니다.
GC Used MemoryC# 관리형 Heap에서 현재 사용 중인 GC 관리 메모리의 양입니다. 지속적으로 증가하면 불필요한 객체 생성이나 참조 유지 여부를 확인할 필요가 있습니다.
GC Allocated In Frame해당 프레임에서 새롭게 할당된 관리형 Heap 메모리의 양입니다. 매 프레임 값이 발생하면 GC가 자주 동작하여 GC 스파이크가 발생할 가능성이 높아질 수 있습니다.

Unity Profiler CPU Usage 하단 패널

Unity Profiler의 CPU Usage 모듈 하단 패널은 선택한 한 프레임에서 CPU가 어떤 작업에 시간을 사용했는지 자세히 분석하는 영역입니다. 위쪽 그래프에서 프레임이 튀는 CPU Spike를 발견한 뒤, 아래 패널을 이용해 어떤 함수가 원인인지 추적합니다. 하단 패널의 표시 방식은 Timeline, Hierarchy, Inverted Hierarchy, Raw Hierarchy 네 가지가 있으며, 같은 CPU 데이터를 서로 다른 관점으로 보여줍니다.

View무엇을 보여주는가주로 사용하는 상황
TimelineCPU 작업을 시간 순서와 막대 길이로 표시어느 시점에 어떤 작업이 오래 걸렸는지 확인, CPU Spike 탐색
Hierarchy함수의 호출 구조와 실행 비용을 표 형태로 표시어떤 함수가 느린지, Calls, GC Alloc 등이 많은지 확인
Inverted Hierarchy호출 관계를 반대로 보여줌비용이 큰 함수를 누가 호출하고 있는지 역추적
Raw Hierarchy호출을 합쳐 정리하지 않고 원래 호출 구조에 가깝게 표시동일 함수가 어떤 경로에서 호출됐는지 세부 분석

Timeline

Timeline은 CPU 작업을 시간의 흐름대로 시각화합니다. 가로로 긴 막대일수록 해당 작업이 CPU를 오래 사용했다는 뜻입니다. Main Thread, Render Thread, Job Worker 등 여러 스레드의 작업 시점도 비교할 수 있어 프레임이 갑자기 느려진 원인을 처음 찾을 때 가장 유용합니다.

Hierarchy

Hierarchy는 PlayerLoop → Update → EnemyAI.Update()처럼 부모 함수와 자식 함수의 호출 관계를 보여줍니다. Total, Self, Calls, GC Alloc 등을 확인할 수 있기 때문에 실제 최적화에서 매우 자주 사용합니다. 특히 GC Alloc으로 정렬하면 매 프레임 메모리를 할당하는 함수를 찾기 쉽습니다.

  • Total: 자식 함수 실행 시간까지 포함한 전체 비용
  • Self: 해당 함수 자체가 사용한 시간
  • Calls: 함수가 호출된 횟수
  • GC Alloc: 해당 작업에서 새로 할당된 관리 메모리

Inverted / Raw Hierarchy

Inverted Hierarchy는 Physics.Raycast()처럼 특정 함수가 비싼 것을 발견했을 때 “이 함수를 누가 호출했는가?”를 거꾸로 추적하는 데 사용합니다. Raw Hierarchy는 같은 호출을 보기 좋게 묶지 않고 실제 호출 구조를 더 세밀하게 확인할 때 사용하므로 일반적인 분석보다는 깊은 디버깅에 적합합니다.

plain text
실전 분석 순서:
Timeline에서 CPU Spike 발견 → Hierarchy에서 느린 함수·Calls·GC Alloc 확인 → 필요하면 Inverted Hierarchy로 호출 원인 추적 → 코드 최적화 → Profiler로 재측정

일반적인 성능 분석에서는 Timeline과 Hierarchy 두 가지를 가장 많이 사용한다고 기억해 두시면 됩니다.

Deep Profile이란?

Deep Profile은 Unity Profiler에서 C# 스크립트 내부의 메서드 호출을 더 세밀하게 추적하는 기능입니다. 일반 Profiler보다 훨씬 자세하게 기록하기 때문에, 특정 Update()가 느린 것뿐 아니라 그 안에서 어떤 메서드가 실제 병목인지까지 확인할 수 있습니다.

어떤 정보를 볼 수 있나?

항목의미
Total / Total %해당 메서드와 하위 메서드 실행 시간을 모두 포함한 비용
Self / Self %해당 메서드 자체에서 직접 사용한 시간
Calls해당 메서드가 호출된 횟수

예를 들어 EnemyAI.Update()의 Total %가 높지만 Self %는 낮다면, Update() 자체보다 그 안에서 호출되는 DetectPlayer()나 Physics.Raycast() 같은 하위 메서드가 실제 병목일 가능성이 높습니다. 반대로 Self %가 높다면 해당 메서드 내부 로직 자체가 무겁다고 볼 수 있습니다. 즉, 흔히 말하는 메서드 병목률은 Total %, Self % 등을 보고 판단합니다.

병목을 찾는 기본 흐름

Total %가 높은 메서드 확인 → Self % 확인 → 하위 메서드 추적 → Calls 확인

특히 Calls가 지나치게 많다면 한 번 실행하는 비용은 작더라도 매 프레임 수백 번 반복되면서 전체 성능을 떨어뜨릴 수 있습니다.

주의점

Deep Profile은 거의 모든 C# 메서드 호출에 계측 코드를 추가하기 때문에 Profiler 자체의 오버헤드가 매우 커집니다. 따라서 Deep Profile을 켠 상태에서 측정한 프레임 시간은 실제 게임 성능과 다를 수 있습니다.

plain text
권장 사용법: 평소에는 일반 Profiler로 큰 병목을 찾고, 원인을 더 세밀하게 확인해야 할 때만 Deep Profile을 잠시 활성화하여 메서드 단위로 분석합니다.

핵심:
Total % = 하위 호출까지 포함한 영향, Self % = 해당 메서드 자체의 비용, Calls = 반복 호출 횟수로 기억하시면 됩니다.

메모리 진단 : Garbage Collector

GC(Garbage Collector)란?

GC(Garbage Collector)는 Heap 영역에 생성된 관리형 객체 중 더 이상 참조되지 않는 데이터를 찾아 메모리에서 해제하는 기능입니다.

GC 스파이크가 발생하는 상황

게임 실행 중 new를 반복하거나 문자열, List, 배열 등의 임시 객체를 계속 생성하면 Heap 영역에 데이터가 쌓이고, GC가 많은 객체와 참조 관계를 한꺼번에 검사·정리하면서 GC 스파이크가 발생할 수 있습니다.
GC 스파이크가 발생하면 GC 작업이 특정 프레임에 집중되면 순간적으로 CPU 사용량이 증가해 프레임 드랍, 화면 끊김, 입력 지연 등의 현상이 발생할 수 있습니다.

최적화 방법

자주 사용하는 객체나 컬렉션은 매번 새로 생성하지 않고 재사용하거나 오브젝트 풀링(Object Pooling)을 적용해야 하며, 더 이상 필요하지 않은 객체는 참조를 제거해 GC가 정상적으로 회수할 수 있도록 관리하는 것이 중요합니다.

※ GC를 발생시키지 않도록 하는 것이 아니라, 최대한 덜 발생하도록 재활용하는 것임

CPU / GPU 최적화

Draw Call은 CPU가 GPU에게 특정 Mesh를 특정 Material/Shader로 화면에 그리라고 전달하는 렌더링 명령입니다.

Draw Call 주요 단계

  • 입력 조립 (Input Assembler): CPU가 보낸 정점(Vertex) 데이터를 모아서 삼각형 같은 기본 도형(Primitive)으로 만듭니다.
  • 정점 셰이더 (Vertex Shader): 각 정점의 위치 좌표를 변환하여 3D 공간을 화면에 맞게 배치합니다.
  • 래스터화 (Rasterization): 3D 도형 데이터를 화면의 2D 픽셀 단위로 쪼개어 채울 자리를 찾습니다.
  • 픽셀 셰이더 (Pixel Shader): 각 픽셀의 색상, 빛, 그림자, 질감 등을 세밀하게 계산합니다.
  • 출력 병합 (Output Merger): 깊이 정보 등을 비교하여 최종 화면에 그려질 픽셀들을 합칩니다.

Static Batching을 이용한 최적화

맵의 건물, 벽, 바닥처럼 게임 중 위치·회전·크기가 변하지 않는 오브젝트는 Static으로 설정하여 Static Batching의 대상이 되도록 할 수 있습니다. Static Batching은 이러한 오브젝트의 Mesh를 렌더링하기 좋은 형태로 묶어 처리하여 CPU가 GPU에 전달해야 하는 Draw Call 및 렌더링 명령의 부담을 줄이는 방법입니다. 단, 같은 Material을 사용하는 등 배칭 조건이 맞아야 효과적으로 묶일 수 있습니다.

※ 움직이지 않는 오브젝트에 한해서 Static을 설정해야 하며, 플레이 중 움직이거나 Transform이 변경되는 오브젝트에는 적합하지 않습니다.

오브젝트를 static으로 설정한 경우 Play 시 Combined Mesh로 변경된다.

Combined Mesh란?

Batching Static을 설정한 오브젝트는 Play 시 Unity가 여러 정적 Mesh를 Combined Mesh로 묶어 렌더링할 수 있습니다. 이를 통해 개별 오브젝트마다 Draw Call을 보내는 부담을 줄여 렌더링 성능을 최적화합니다.

2D Rendering 최적화 : Sprite Atlas를 활용한 Draw Call 감소

Sprite Atlas란?

Sprite Atlas는 여러 개의 Sprite Texture를 하나의 큰 Texture로 묶어서 사용하는 방식입니다. 2D 게임에서는 Sprite마다 다른 Texture를 사용할 경우 Texture가 변경될 때마다 렌더링 Batch가 분리될 수 있는데, Atlas를 사용하면 여러 Sprite가 같은 Texture를 공유할 수 있어 Draw Call을 줄이는 데 도움이 됩니다.

Draw Call이 증가하는 상황

캐릭터, 배경, 아이콘 등의 Sprite가 각각 다른 Texture나 Material을 사용하면 렌더링 도중 Texture 또는 Material을 계속 교체해야 하므로 Batch가 나뉘고 Draw Call이 증가할 수 있습니다. 오브젝트가 많을수록 이러한 상태 변경 비용도 커질 수 있습니다.

최적화 방법

같은 화면에서 자주 함께 렌더링되는 Sprite들을 Sprite Atlas로 묶어 하나의 Texture를 공유하도록 구성하면 여러 Sprite를 같은 Batch로 처리하기 쉬워져 Draw Call을 줄일 수 있습니다.

LOD

LOD란?

LOD(Level of Detail)는 카메라와의 거리에 따라 오브젝트의 Mesh 복잡도를 단계적으로 낮추는 최적화 기법입니다. 가까이 있는 오브젝트는 디테일이 많은 Mesh를 사용하고, 멀리 있는 오브젝트는 정점과 삼각형 수가 적은 Mesh로 교체하여 GPU의 렌더링 부하를 줄입니다.

예를 들어 나무 하나를 다음처럼 구성할 수 있습니다.

yaml
가까움        → LOD 0 : 고품질 Mesh

조금 멀어짐 → LOD 1 : 중간 품질 Mesh

멀리 있음 → LOD 2 : 저품질 Mesh

매우 멀리 있음 → Culled : 렌더링하지 않음

스크린샷의 LOD 설정

현재 설정은 화면에서 오브젝트가 차지하는 크기(Screen Size)를 기준으로 LOD가 변경됩니다.

단계Screen Size동작
LOD 060% 이상가장 디테일한 Mesh 사용
LOD 130 ~ 60%중간 품질 Mesh 사용
LOD 210 ~ 30%저품질 Mesh 사용
Culled10% 미만오브젝트를 렌더링하지 않음

즉, 단순히 몇 m 떨어졌는지로 판단하는 것이 아니라 카메라 화면에서 오브젝트가 얼마나 크게 보이는지를 기준으로 LOD가 전환됩니다.

주요 옵션

  • Fade Mode : None
    LOD가 바뀔 때 페이드 효과 없이 바로 다음 Mesh로 전환합니다.
  • Transition (% Screen Size)
    각 LOD 단계로 전환되는 화면상 크기를 설정합니다.
  • Renderers
    해당 LOD 단계에서 실제로 보여줄 Mesh Renderer 등을 등록하는 곳입니다. 현재 스크린샷에서는 모두 List is Empty 상태이므로 아직 Mesh가 등록되지 않은 상태입니다.
  • Culled
    설정된 최소 크기보다 작아지면 해당 오브젝트를 아예 렌더링하지 않습니다.

Fade Mode란?

LOD가 LOD 0 → LOD 1 → LOD 2처럼 바뀔 때 Mesh가 전환되는 방식을 설정하는 옵션입니다. 갑자기 Mesh가 바뀌면서 튀어 보이는 Popping 현상을 줄이기 위해 사용합니다.

옵션설명
None전환 효과 없이 다음 LOD Mesh로 즉시 변경됩니다. 가장 단순하고 비용이 적습니다.
Cross Fade이전 LOD와 다음 LOD를 일정 구간 동안 겹쳐서 점진적으로 전환합니다. LOD 변경이 자연스럽지만 약간의 추가 렌더링 비용이 발생할 수 있습니다.
Speed TreeUnity의 SpeedTree 전용 LOD 전환 방식입니다. 나무의 형태가 단계적으로 자연스럽게 변화하도록 처리합니다.

Occlusion Culling이란?

Occlusion Culling은 카메라 시야 안에 있더라도 벽이나 건물 등에 완전히 가려진 오브젝트는 렌더링하지 않는 최적화 기법입니다.

구분기준특징
Frustum Culling카메라 시야 밖Unity에서 자동 적용
Occlusion Culling다른 오브젝트에 가려짐Bake 필요

적용 방법

  • 벽, 건물 등 가리는 오브젝트 → Occluder Static
  • 가려지는 정적 오브젝트 → Occludee Static
  • Window > Rendering > Occlusion Culling에서 Bake

효과

화면에 실제로 보이지 않는 오브젝트의 렌더링을 생략하여 Draw Call과 CPU/GPU 렌더링 부하를 줄일 수 있습니다.

※ 정리하면 Batching은 묶어서 그리기, LOD는 멀면 단순하게 그리기, Occlusion Culling은 안 보이면 그리지 않기입니다.

적용 방법

  1. Window > Rendering > Occlusion Culling 창을 엽니다.
  2. 벽이나 건물처럼 움직이지 않으며 다른 오브젝트를 가리는 오브젝트에 Occluder Static을 설정합니다.
  3. 가려질 수 있는 정적 맵 오브젝트에는 Occludee Static을 설정합니다.
  4. 필요한 경우 Occlusion Area를 배치하여 카메라가 이동하는 영역을 지정합니다.
  5. Bake를 실행하여 어떤 위치에서 어떤 오브젝트가 가려지는지에 대한 데이터를 생성합니다.

Occlusion Culling을 통해 Occludee Static이 설정된 오브젝트는 카메라 밖에 나왔을 때 그리지 않음.

Lightmap Bake란?

Lightmap Bake는 움직이지 않는 오브젝트에 대한 조명과 그림자 계산 결과를 미리 텍스처에 저장해두는 방식입니다. 실시간으로 조명을 계속 계산하지 않고, 미리 계산된 Lightmap을 사용하기 때문에 실행 중 CPU/GPU의 조명 연산 부담을 줄일 수 있습니다.

적용 대상

  • 벽, 바닥, 건물처럼 움직이지 않는 오브젝트
  • 고정된 조명 환경
  • 실내 맵이나 정적인 배경

보통 해당 오브젝트를 Contribute GI 대상으로 설정하고, Light를 Baked 또는 환경에 따라 Mixed로 설정한 뒤 Lighting 창에서 Bake를 진행합니다.

효과

실시간 조명 계산량을 줄여 렌더링 성능을 향상시킬 수 있으며, 간접광이나 부드러운 그림자도 미리 계산할 수 있습니다.

※ 단점은 Lightmap 텍스처가 추가되므로 메모리와 빌드 용량이 증가할 수 있고, Bake된 오브젝트나 조명이 크게 움직이는 환경에는 적합하지 않습니다.

Object Pool이란?

Object Pool은 자주 생성되고 제거되는 오브젝트를 미리 일정 수 생성해두고, 필요할 때 꺼내서 사용한 뒤 다시 반환하여 재사용하는 방식입니다. 반복적으로 Instantiate와 Destroy를 호출하지 않기 때문에 실행 중 오브젝트 생성/삭제에 따른 CPU 부하와 GC 발생을 줄일 수 있습니다.

적용 대상

  • 총알, 투사체처럼 짧은 시간 동안 반복해서 생성되는 오브젝트
  • 몬스터, 이펙트, 파티클 등 반복적으로 등장하는 오브젝트
  • UI 팝업이나 데미지 텍스트처럼 자주 생성되고 사라지는 요소

보통 게임 시작 시 필요한 오브젝트를 Pool에 미리 생성해두고 비활성화 상태로 보관합니다. 사용 시 Pool에서 오브젝트를 가져와 활성화하고, 사용이 끝나면 Destroy하지 않고 다시 비활성화하여 Pool에 반환합니다.

효과

반복적인 오브젝트 생성과 삭제를 줄여 CPU 사용량과 메모리 할당을 감소시키고, GC로 인한 프레임 드롭을 완화할 수 있습니다. 특히 짧은 시간에 많은 오브젝트가 생성되는 상황에서 효과적입니다.

※ 단점은 사용량보다 너무 많은 오브젝트를 미리 생성하면 불필요한 메모리를 차지할 수 있고, Pool의 크기와 오브젝트 반환 상태를 별도로 관리해야 한다는 점입니다.