Unity Profiler CPU/GPU 분석
Unity 프로파일러는 CPU·GPU·메모리·렌더링 병목을 찾는 1차 진단 도구입니다. “느리다”는 현상에서 “왜 느린가”로 이동하려면 프로파일러 데이터를 체계적으로 읽는 방법을 알아야 합니다.
1. Profiler 기본 워크플로
섹션 제목: “1. Profiler 기본 워크플로”Window > Analysis > Profiler (단축키 Ctrl+7)Deep Profile 모드: 모든 C# 메서드 호출을 계측합니다. 오버헤드가 크므로 의심 영역을 좁힌 후 사용합니다.
1.1 커스텀 샘플 마커
섹션 제목: “1.1 커스텀 샘플 마커”코드에서 직접 마커를 삽입하면 프로파일러 타임라인에 구간이 표시됩니다.
using Unity.Profiling;
public class EnemyManager : MonoBehaviour{ // static으로 선언해 매 프레임 할당을 피함 private static readonly ProfilerMarker s_UpdateMarker = new ProfilerMarker(ProfilerCategory.Scripts, "EnemyManager.UpdateAll");
private static readonly ProfilerMarker s_PathfindMarker = new ProfilerMarker(ProfilerCategory.AI, "EnemyManager.Pathfind");
void Update() { using (s_UpdateMarker.Auto()) { foreach (var enemy in _activeEnemies) { using (s_PathfindMarker.Auto()) enemy.UpdatePath();
enemy.UpdateAnimation(); } } }}ProfilerMarker는 Development Build에서만 활성화되며 Release 빌드에서는 zero-cost로 제거됩니다.
2. CPU 병목 패턴과 해결책
섹션 제목: “2. CPU 병목 패턴과 해결책”2.1 메인 스레드 블로킹
섹션 제목: “2.1 메인 스레드 블로킹”// 프로파일러에서 이런 패턴이 보이면:// MainThread: [Physics.Step 18ms] [Render.OpaqueGeometry 4ms]// → Physics가 CPU 예산을 독점// 해결: Physics.simulationMode를 Script로 전환하고 분산 실행void FixedUpdate(){ // 무거운 Physics는 멀티 스텝으로 분할 Physics.Simulate(Time.fixedDeltaTime * 0.5f); // 중간에 다른 로직 처리 Physics.Simulate(Time.fixedDeltaTime * 0.5f);}2.2 GC.Alloc 스파이크
섹션 제목: “2.2 GC.Alloc 스파이크”프로파일러 Memory 트랙에서 GC.Alloc이 빈번하게 보이면:
// 나쁜 예: 매 프레임 문자열 생성void Update(){ debugText.text = "Score: " + score.ToString(); // 박싱 + 문자열 연결}
// 좋은 예: StringBuilder 재사용private readonly System.Text.StringBuilder _sb = new(64);
void Update(){ _sb.Clear(); _sb.Append("Score: "); _sb.Append(score); debugText.text = _sb.ToString();}3. GPU 병목 분석
섹션 제목: “3. GPU 병목 분석”3.1 Frame Debugger
섹션 제목: “3.1 Frame Debugger”Window > Analysis > Frame DebuggerFrame Debugger는 각 드로우콜을 순서대로 재생하며 어떤 오브젝트가 얼마나 많은 드로우콜을 유발하는지 보여줍니다.
확인 포인트:
- Shadow Caster 드로우콜이 렌더링 드로우콜의 2배인 경우 →
Cast Shadows를Off로 설정 - 동일 메시가 여러 번 그려지는 경우 → GPU Instancing 또는 Static Batching 검토
3.2 GPU Instancing 적용
섹션 제목: “3.2 GPU Instancing 적용”// Material 설정에서 Enable GPU Instancing 체크 후// 코드에서 MaterialPropertyBlock 사용
public class GrassRenderer : MonoBehaviour{ [SerializeField] private Mesh grassMesh; [SerializeField] private Material grassMaterial;
private Matrix4x4[] _matrices; private MaterialPropertyBlock _mpb;
void Start() { _matrices = new Matrix4x4[1023]; // DrawMeshInstanced 최대 1023 _mpb = new MaterialPropertyBlock();
for (int i = 0; i < _matrices.Length; i++) { _matrices[i] = Matrix4x4.TRS( new Vector3(Random.Range(-50f, 50f), 0, Random.Range(-50f, 50f)), Quaternion.identity, Vector3.one ); } }
void Update() { Graphics.DrawMeshInstanced(grassMesh, 0, grassMaterial, _matrices, _matrices.Length, _mpb); }}4. Rendering Stats 읽기
섹션 제목: “4. Rendering Stats 읽기”Stats 패널 (Game 뷰 우상단 "Stats" 버튼)| 항목 | 설명 | 권장 기준 (모바일) |
|---|---|---|
| Batches | 드로우콜 수 | < 100 |
| Tris | 삼각형 수 | < 300K |
| Verts | 정점 수 | < 150K |
| SetPass Calls | 셰이더 패스 전환 | < 50 |
4.1 Render Pipeline Stats (URP)
섹션 제목: “4.1 Render Pipeline Stats (URP)”// 런타임에서 렌더링 통계 수집using UnityEngine.Rendering;using UnityEngine.Rendering.Universal;
void LogRenderStats(){ // URP DebugManager 활용 var debugManager = DebugManager.instance; // 또는 Unity Statistics API Debug.Log($"Draw Calls: {UnityStats.drawCalls}"); Debug.Log($"Triangles: {UnityStats.triangles}");}5. Memory Profiler 활용
섹션 제목: “5. Memory Profiler 활용”Window > Analysis > Memory Profiler (패키지 설치 필요)
// 스냅샷 코드에서 수동 요청using Unity.Profiling.Memory;
public class MemoryDebugger : MonoBehaviour{ [ContextMenu("Take Memory Snapshot")] void TakeSnapshot() { MemoryProfiler.TakeSnapshot("snapshot", OnSnapshotFinished, CaptureFlags.ManagedObjects | CaptureFlags.NativeObjects); }
void OnSnapshotFinished(string path, bool success) { Debug.Log(success ? $"Snapshot saved: {path}" : "Snapshot failed"); }}ProfilerMarker를 코드 핵심 경로에 삽입하면 타임라인에서 정확한 병목 구간을 확인할 수 있다.- CPU 스파이크의 가장 흔한 원인은 GC.Alloc이다. 핫 경로에서
string연결과 LINQ를 피한다. - GPU 병목은 Frame Debugger로 드로우콜 과다 여부를 먼저 확인하고, GPU Instancing과 Batching으로 대응한다.
- Stats 패널의 SetPass Calls가 Batches보다 중요한 지표인 경우가 많다. 셰이더 variant 수를 최소화한다.