LYSC STUDIO

LIST
Cover

Unity Profiler와 Frame Debugger를 이용한 CPU/GPU 바운드 병목 역추적 및 해결 가이드

·Unity Optimization

게임 프레임이 떨어질 때 단순히 감각에 의존하는 대신, 프로파일러와 프레임 디버거를 사용하여 정확하게 CPU와 GPU 바운드를 진단하고 병목을 역추적하는 실무 가이드를 제공합니다.

도입 및 개요

프로그래밍의 전설 도널드 크누스(Donald Knuth)는 '설익은 최적화는 모든 악의 근원이다'라고 말했습니다. 게임 개발에 있어 이 명제는 '추측하지 말고 측정하라'는 의미로 직결됩니다. 프레임 드랍이 발생했을 때 많은 인디 개발자들이 직감에 의존하여 코드를 수정하거나 텍스처 해상도를 낮추는 우를 범합니다. 하지만 병목의 원인이 CPU의 무거운 연산(CPU Bound)인지, GPU의 과도한 렌더링 부하(GPU Bound)인지 정확히 판별하지 않고 진행하는 최적화는 시간 낭비일 뿐만 아니라 버그를 양산할 위험이 큽니다. 유니티가 제공하는 기본 도구인 Unity Profiler와 Frame Debugger는 프로젝트의 내부 장기를 들여다볼 수 있는 엑스레이 및 MRI와 같으며, 이 도구들을 능숙하게 다루는 것은 전문 개발자로 거듭나기 위한 필수 관문입니다.

프로파일링의 첫 단계는 병목의 주체를 식별하는 것입니다. Unity Profiler를 열고 게임을 실행(가능하다면 에디터가 아닌 타겟 디바이스에 연결하여 프로파일링하는 것이 훨씬 정확합니다)하여 CPU Usage 모듈을 관찰합니다. 프레임 타임이 급증하는 뾰족한 지점, 즉 스파이크(Spike)가 발생한 프레임을 클릭하여 하단의 Hierarchy 뷰를 분석합니다. 여기서 렌더링 스레드의 대기 시간(Gfx.WaitForPresentOnGfxThread 등)이 비정상적으로 높다면 이는 십중팔구 GPU 바운드입니다. 즉, CPU는 모든 연산을 마쳤지만 GPU가 화면을 다 그릴 때까지 빈둥거리며 기다리고 있다는 뜻입니다. 반대로 MonoBehaviour.Update나 특정 커스텀 스크립트 내의 연산 시간이 프레임 타임의 대부분을 차지하고 있다면 명백한 CPU 바운드입니다. CPU 바운드라면 복잡한 물리 연산, 과도한 AI 경로 탐색(NavMesh), 비효율적인 루프 검색(FindObjectsOfType 등)이 원인인지 하이어라키를 파고들어 정확한 범인을 색출해내야 합니다.

CPU 병목의 범인을 더 정밀하게 추적하기 위해서는 코드 내에 명시적인 프로파일링 마커를 삽입해야 합니다. UnityEngine.Profiling 네임스페이스의 Profiler.BeginSample("식별문자열")과 Profiler.EndSample() 메서드로 의심되는 무거운 함수 영역을 감싸면, 프로파일러 타임라인에 해당 블록이 고유한 이름으로 표시되어 정확히 몇 밀리초(ms)를 소비하는지 측정할 수 있습니다. 예를 들어 복잡한 타일맵 생성 로직이나 네트워크 데이터 파싱 로직에 샘플을 적용하면, 단순히 Update() 함수 내부라는 막연한 정보가 아니라 정확히 어느 루프의 어느 구문에서 딜레이가 발생하는지 핀포인트로 파악할 수 있습니다. 이러한 정량적 데이터를 바탕으로 알고리즘을 개선하거나 로직을 여러 프레임에 걸쳐 분산(Coroutine 활용)시키는 전략을 수립할 수 있습니다.

반면, 병목이 GPU 바운드로 판명되었다면 Frame Debugger의 출격 타이밍입니다. 프레임 디버거를 활성화하면 한 프레임이 화면에 완성되기까지 GPU에게 내려진 모든 드로우 콜을 순차적으로 재생하며 시각적으로 확인할 수 있습니다. 리스트를 하나씩 클릭해보며 화면에 무엇이 렌더링되는지 추적하다 보면 충격적인 사실을 발견하게 될 때가 많습니다. 예를 들어, 투명한(UI나 파티클) 오브젝트들이 화면을 너무 여러 번 덧칠하여 픽셀을 채우는 오버드로우(Overdraw) 현상이 극심하다거나, 백그라운드의 보이지 않는 작은 돌멩이 하나를 그리기 위해 값비싼 포스트 프로세싱 연산이 수행되고 있는 식입니다. 배칭(Batching)이 깨진 원인(Why this draw call cannot be batched?)을 프레임 디버거의 상세 정보에서 확인하고, 텍스처 아틀라스(Atlas)를 묶거나 공유 머티리얼을 사용하도록 애셋 구조를 변경함으로써 렌더링 파이프라인의 숨통을 트이게 할 수 있습니다.

Performance & FPS Simulator
Current FPS
60.0
Implementation C# / Unity
using UnityEngine;
using UnityEngine.Profiling;

public class HeavyAIController : MonoBehaviour
{
    void Update()
    {
        Profiler.BeginSample("AI_PathFinding_Calculations");
        PerformComplexPathfinding();
        Profiler.EndSample();

        Profiler.BeginSample("AI_Vision_Check");
        CheckLineOfSight();
        Profiler.EndSample();
    }

    private void PerformComplexPathfinding() { /* 무거운 A* 알고리즘 등 */ }
    private void CheckLineOfSight() { /* 무거운 Raycast 등 */ }
}
최적화는 장님이 코끼리를 만지는 과정이 되어서는 안 됩니다. 프로파일러와 프레임 디버거라는 명확한 나침반을 통해 데이터에 기반한 엔지니어링을 실천하시길 바랍니다.