"게임이 끊겨요" 티켓 진단하기 ② — Profiler와 Frame Timing으로 범인 확정하기

해상도 테스트, Profiler 읽는 법, FrameTimingManager로 CPU/GPU 시간 분리까지. 체감을 계측 데이터로 바꾸는 실전 절차입니다.

  • unity
  • performance
  • profiler

"끊겨요" 티켓 진단 3부작 — ① 원인 4분류 · ② 계측 실전 · ③ 발생 순간 캡처 (③ 공개 예정)

앞 편에서 프레임 드랍을 네 유형(CPU/GPU/GC/로딩)으로 분류했습니다. 이번 편은 그 분류를 추측이 아니라 데이터로 확정하는 절차입니다.

0. 계측 전에: 에디터 수치를 믿지 않는다

에디터의 프레임 타임에는 에디터 자체 오버헤드(인스펙터 갱신, 씬 뷰, 프로파일러 UI)가 섞입니다. 특히 Deep Profile은 모든 함수 호출에 계측 코드를 끼워 넣어 프로파일링 자체가 병목을 만들어냅니다 — 에디터에서 무겁던 함수가 빌드에서는 문제가 아닌 경우가 흔합니다.

원칙은 하나입니다. 판정은 development build에서, 에디터는 탐색용으로만.

1. 30초 판별법 — 해상도 스위치

계측 도구를 열기 전에 가장 싼 실험부터 합니다.

// 렌더 해상도를 절반으로 — GPU 부하만 선택적으로 줄인다
Screen.SetResolution(Screen.width / 2, Screen.height / 2, Screen.fullScreenMode);
  • 눈에 띄게 좋아진다 → GPU 바운드. 셰이더·오버드로우·드로우콜 쪽입니다.
  • 변화가 없다 → CPU 쪽. 스크립트·물리·GC로 범위가 좁혀집니다.

2. Profiler 읽기 — 어디를 보는가

Window → Analysis → Profiler에서 실제로 봐야 할 곳은 세 군데입니다.

CPU Usage의 Timeline 뷰 — Hierarchy 뷰보다 먼저 봅니다. 메인 스레드 막대에서 무엇이 프레임을 채우는지, 렌더 스레드가 놀고 있는지가 한눈에 보입니다. Gfx.WaitForPresent·WaitForTargetFPS가 길면 CPU는 여유 있는데 GPU(또는 VSync)를 기다리는 상태 — GPU 바운드의 전형입니다.

GC Alloc 컬럼 — Hierarchy 뷰에서 프레임당 관리 힙 할당을 보여줍니다. 매 프레임 0이 아닌 값이 꾸준히 찍히는 함수가 GC 스파이크의 공급원입니다. 스파이크가 찍힌 프레임이 아니라 평상시 프레임의 할당을 봐야 합니다 — 범인은 스파이크 프레임에 없습니다.

프레임 타임 그래프의 모양 — 앞 편의 시그니처가 여기서 그대로 보입니다. 전반적으로 높으면 바운드 문제, 주기적 바늘이면 GC, 단발성 벽이면 로딩 히치.

3. FrameTimingManager — CPU/GPU 시간을 숫자로 분리

Profiler를 붙일 수 없는 환경(QA 머신, 배포 빌드)에서는 런타임 API로 같은 판정을 할 수 있습니다.

// Project Settings → Player → Frame Timing Stats 활성화 필요 (그 전엔 0 이 나온다)
FrameTiming[] timings = new FrameTiming[1];

void Update()
{
    FrameTimingManager.CaptureFrameTimings();
    if (FrameTimingManager.GetLatestTimings(1, timings) == 0) return;

    double cpuMs = timings[0].cpuFrameTime;
    double gpuMs = timings[0].gpuFrameTime;
    // gpuMs 가 cpuMs 보다 지속적으로 크면 GPU 바운드,
    // 반대면 CPU 바운드 — 추측이 숫자가 된다
}

지원 플랫폼·그래픽 API 제약이 있고 미지원 환경에서는 0을 반환하므로, 0 체크를 생략하면 "모든 프레임이 0ms"라는 이상한 데이터를 얻게 됩니다.

4. 실사례 — 범인을 추측으로 정했다가 틀린 이야기

저희가 겪은 사례입니다. Windows 빌드에서만 스터터링 리포트가 왔고, 첫 용의자는 저희 SDK의 상시 캡처였습니다. 상식적인 추측이었습니다 — 상시로 도는 게 그것뿐이었으니까요.

계측 결과는 반대였습니다. SDK를 완전히 꺼도 스터터링은 동일했고, 프레임 타임을 분리해 보니 GPU 쪽이 병목이었습니다. 추측대로 SDK를 "최적화"했다면 아무 효과 없는 작업에 시간을 쓰고, 진짜 원인은 그대로 남았을 겁니다.

교훈은 단순합니다. 성능 진단에서 정황과 체감은 자주 틀리고, 계측은 싸다. 해상도 스위치 실험은 30초면 됩니다.

남는 문제

여기까지의 도구에는 공통 전제가 있습니다 — 문제가 발생하는 그 순간에 계측이 붙어 있어야 한다는 것. 하지만 성능 티켓은 QA 머신에서, 개발자가 없을 때, Profiler 없이 발생합니다. "끊겼어요"라는 체감 보고를 받은 뒤에 계측을 붙이면, 앞 글에서 다룬 "녹화 버튼은 항상 늦게 눌린다" 문제로 되돌아갑니다.

발생 순간의 성능 데이터를 미리, 항상, 가볍게 수집해 두는 방법이 마지막 편의 주제입니다.