학습 목표
- 실시간 게임에서 프레임 예산을 인지하고 설계·구현 단계부터 성능을 고려한다.
- CPU·GPU·메모리·I/O 병목을 측정 → 원인 규명 → 개선 루프로 해결한다.
- OOP의 편의성과 데이터 지향 설계(DoD) 의 성능 사이에서 현실적인 균형을 잡는다.
1️⃣ 성능이 중요한 이유 — 프레임 예산과 체감 품질
- 게임은 “정해진 시간 안에 모든 작업을 끝내야 하는” 하드 리얼타임에 가깝다.
- 60FPS: 16.67ms, 120FPS: 8.33ms, VR(90/120+): 이미지 지연과 멀미 위험 ↑
- 일관성이 핵심: 평균 10ms, 가끔 40ms 스파이크면 유저는 “렉”으로 느낀다.
- 프레임 예산은 보통
입력(1ms) + 게임로직(8~10ms) + 렌더(4~6ms) + 기타(1ms) 식으로 배분한다.
- 한 서브시스템이 과도하게 늘어나면 다른 시스템을 깎거나 비동기화해야 한다.
void GameLoop() {
while (running) {
const float dt = timer.GetDeltaTime();
ProcessInput(dt); // 목표 ~1ms
UpdateLogic(dt); // 목표 ~10ms
Render(); // 목표 ~5ms
if (dt > 0.0167f) LogDrop(dt);
}
}
체크리스트
- [ ] 평균 프레임 타임, P95/P99 스파이크를 별도로 본다.
- [ ] FPS가 아니라 ms 단위로 대화한다(정확한 예산 감각).
2️⃣ 병목 4형제 — CPU·GPU·메모리·I/O
- CPU 병목: AI/경로탐색/물리/애니/스크립트 등 직렬 비용이 큼.
- 해결: 작업 분할(프레임 스케줄링), 델타 업데이트, 쓰레딩(락 최소화)
- GPU 병목: 드로우콜 과다, 오버드로우, 복잡한 픽셀 셰이더, 불필요한 포스트 프로세스.
- 해결: 인스턴싱, 머티리얼 단순화, 해상도 스케일, UI 오버드로우 점검
- 메모리 병목: 캐시 미스, 동적 할당 스파이크, 데이터 불연속.
- 해결: SoA/타이트 패킹, 풀링, 재할당 최소화(reserve)
- I/O 병목: 씬/텍스처 스트리밍, 디스크 접근, 네트워크 RTT/패킷 처리.
- 해결: 사전 프리페치, 압축/디코드 비동기, 버퍼링
// AI를 프레임 분할 처리(스파이크 완화)
static size_t idx = 0;
const int kPerFrame = 12;
for (int i = 0; i < kPerFrame && !enemies.empty(); ++i) {
enemies[(idx + i) % enemies.size()].TickAI();
}
idx = (idx + kPerFrame) % enemies.size();