PART 6 · 강의 3/3

성능 최적화

Mass Entity 시스템의 성능을 극대화하는 프로파일링과 최적화 전략을 학습합니다

01

프로파일링

병목 구간을 정확히 측정하고 분석하는 방법

프로파일링 도구와 접근법 // 1단계: stat Mass로 전체 개요 확인 stat Mass // → 총 엔티티 수, Archetype 수, Processor 실행 시간 // 2단계: Unreal Insights로 상세 분석 // → 각 Processor의 실행 시간 추적 // → 스레드별 부하 분포 확인 // → Trace 시작: UnrealInsights.exe -Store // 3단계: CSV 프로파일러로 장기 추세 // → csv.profiler.start // → csv.profiler.stop // 핵심 지표: // - Frame Time: 전체 프레임 소요 시간 // - Mass Total: Mass 시스템 전체 시간 // - Per Processor: 개별 Processor 시간 // - Entity Count: 처리 엔티티 수 // - Archetype Count: Archetype 수
병목 식별 순서

1. stat Mass로 전체 소요 시간 확인 → 2. 가장 느린 Processor 식별 → 3. 해당 Processor의 처리 엔티티 수 확인 → 4. Simulation LOD로 줄일 수 있는지 검토 → 5. Processor 로직 최적화

02

메모리 최적화

Fragment 설계와 Archetype 관리를 통한 메모리 절감

Fragment 크기 최소화

  • FVector3f(12B) 대신 FVector3f를 고려
  • 불필요한 패딩 제거
  • bool 대신 Tag 사용
  • enum은 uint8로
  • 읽기 전용 데이터는 SharedFragment

Archetype 파편화 방지

  • 불필요한 Tag 변형 최소화
  • 동적 Fragment 추가/제거 자제
  • 비슷한 엔티티는 같은 Config 공유
  • Mass Debugger로 Archetype 수 모니터링
  • 목표: 50개 이하의 활성 Archetype
C++ - Fragment 크기 최적화 예시 // 나쁜 예: 불필요하게 큰 Fragment struct FBadFragment : public FMassFragment { FVector Position; // 24 bytes (double) FVector Velocity; // 24 bytes FString Name; // 16+ bytes (힙 할당!) bool bIsAlive; // 1 byte + 7 padding bool bIsMoving; // 1 byte + 7 padding // 총: ~80+ bytes per entity }; // 좋은 예: 최적화된 Fragment 설계 struct FGoodTransformFragment : public FMassFragment { FVector3f Position; // 12 bytes (float) FQuat4f Rotation; // 16 bytes // 총: 28 bytes }; struct FGoodVelocityFragment : public FMassFragment { FVector3f Value; // 12 bytes }; // bIsAlive → FIsAliveTag (0 bytes) // bIsMoving → FIsMovingTag (0 bytes) // Name → FMassSharedFragment에 저장 // 총: 40 bytes (50% 절감)
03

스레드 최적화

멀티스레드 병렬 처리를 극대화하는 전략

병렬화 최적화 전략 // 1. ReadOnly 접근 극대화 // ReadOnly Fragment가 많을수록 더 많은 Processor가 병렬 실행 // → 꼭 필요한 Fragment만 ReadWrite로 설정 // 2. bRequiresGameThreadExecution 최소화 // 게임 스레드 필수 Processor를 최소화하여 // 워커 스레드에서 병렬 실행 극대화 // 3. 큰 작업 분할 // 하나의 무거운 Processor 대신 // 여러 가벼운 Processor로 분할하여 병렬화 // 4. Fragment 분리 // 같은 Fragment에 ReadWrite 접근하는 Processor가 // 많으면 해당 Fragment를 분리 // 예시: 하나의 큰 CombatFragment를 // FAttackFragment + FDefenseFragment로 분리하면 // 공격 Processor와 방어 Processor가 병렬 실행 가능
C++ - 병렬 최적화 패턴 // 게임 스레드 의존성을 별도 Processor로 분리 // 1단계: 데이터 수집 (워커 스레드에서 병렬 실행) UCLASS() class UCollectDataProcessor : public UMassProcessor { UCollectDataProcessor() { bRequiresGameThreadExecution = false; // ReadOnly 접근으로 병렬 실행 허용 } }; // 2단계: 월드 상호작용 (게임 스레드에서만) UCLASS() class UApplyToWorldProcessor : public UMassProcessor { UApplyToWorldProcessor() { bRequiresGameThreadExecution = true; ExecutionOrder.ExecuteAfter.Add( TEXT("UCollectDataProcessor")); // 최소한의 작업만 게임 스레드에서 수행 } };
Unreal Insights 활용

Unreal Insights의 타이밍 뷰에서 각 스레드의 Processor 실행을 시각적으로 확인하세요. 한 스레드만 바쁘고 나머지가 놀고 있다면 병렬화가 부족한 것입니다.

04

실전 최적화 체크리스트

프로젝트에 적용할 종합 최적화 가이드

최적화 체크리스트 // ═══════════════════════════════════════════ // Fragment 최적화 // ═══════════════════════════════════════════ // ☐ Fragment 크기를 64바이트 이하로 유지 // ☐ 자주 접근하는 데이터를 작은 Fragment에 분리 // ☐ bool 변수 대신 Tag 사용 // ☐ 공유 데이터는 SharedFragment 활용 // ☐ FString, TArray 등 힙 할당 타입 지양 // // ═══════════════════════════════════════════ // Processor 최적화 // ═══════════════════════════════════════════ // ☐ bRequiresGameThreadExecution = false 기본 // ☐ ReadOnly 접근 극대화 // ☐ Simulation LOD로 처리 빈도 제어 // ☐ 무거운 Processor는 분할하여 병렬화 // ☐ 매 틱 불필요한 연산 제거 // // ═══════════════════════════════════════════ // Archetype 최적화 // ═══════════════════════════════════════════ // ☐ Archetype 수를 50개 이하로 관리 // ☐ 불필요한 동적 Tag/Fragment 변경 최소화 // ☐ 비슷한 엔티티는 같은 Config 공유 // // ═══════════════════════════════════════════ // 시각화 최적화 // ═══════════════════════════════════════════ // ☐ LOD 거리 적절히 설정 // ☐ Actor Pool 크기 최적화 // ☐ ISM 메시 복잡도 관리 // ☐ Frustum/Occlusion 컬링 활용 // // ═══════════════════════════════════════════ // 일반 // ═══════════════════════════════════════════ // ☐ stat Mass로 정기적 프로파일링 // ☐ Unreal Insights로 스레드 분석 // ☐ 목표 FPS 기준으로 엔티티 수 조정 // ☐ 점진적 최적화 (측정 → 분석 → 개선)
조기 최적화 주의

코드가 동작하기 전에 최적화하지 마세요. 먼저 정확하게 동작하는 시스템을 구축한 후, 프로파일링 데이터를 기반으로 실제 병목 구간만 최적화하세요. Mass Entity 자체가 이미 고성능 프레임워크이므로, 올바르게 사용하기만 해도 대부분의 경우 충분합니다.

SUMMARY

핵심 요약

  • stat MassUnreal Insights로 프로파일링하여 실제 병목 구간을 측정한다
  • Fragment 크기를 64바이트 이하로 유지하고, bool 대신 Tag, 공유 데이터는 SharedFragment를 사용한다
  • ReadOnly 접근 극대화와 bRequiresGameThreadExecution 최소화로 병렬 처리를 극대화한다
  • Archetype 파편화를 50개 이하로 관리하고, 불필요한 동적 Tag/Fragment 변경을 최소화한다
  • LOD 거리, Actor Pool 크기, ISM 메시 복잡도를 적절히 설정하여 렌더링을 최적화한다
  • 프로파일링 데이터 기반의 점진적 최적화가 핵심이며, 조기 최적화는 피한다
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: Fragment 크기 최적화

Fragment의 데이터 타입을 최적화하세요. float 대신 half, FVector 대신 FVector3f, bool 대신 Tag 사용 등으로 Fragment 크기를 줄여 Chunk당 엔티티 수를 늘리고 캐시 효율을 높이세요.

실습 2: Query 최적화

불필요한 ReadWrite 접근을 ReadOnly로 변경하고, ChunkCondition으로 불필요한 Chunk를 건너뛰도록 Query를 최적화하세요. 최적화 전후 프로파일 결과를 비교하세요.

심화 과제: 메모리와 CPU 종합 최적화

stat MassEntity와 Insights로 전체 Mass Entity 시스템을 프로파일링하여, Archetype 수 줄이기(Fragment 조합 통합), Processor 병합, Shared Fragment 활용 등의 종합 최적화를 수행하고 성능 리포트를 작성하세요.