TIL [25.07.31] - Lost In Eden 건매니저 리팩토링

chooha·2025년 7월 30일

TIL

목록 보기
55/60

📝 개발일지 - GunManager 성능 및 안전성 최적화

오늘은 어제 해결한 GunManager nullptr 문제에 이어서 성능과 안전성 측면에서 추가 리팩토링을 진행했음

기존 방식은 TArray를 값으로 반환하여 매번 복사 비용이 발생하고, 포인터 매개변수로 인한 null 안전성 문제가 있었음

새로운 방식은 const 참조 반환과 캐시 시스템으로 성능을 최적화하고, 참조 매개변수 사용으로 컴파일 타임 null 안전성을 확보하는 구조로 개선했음


💡 오늘의 5분 기록

1. 성능 병목지점 발견 🔍

코드 리뷰 중에 GunManager의 성능과 안전성 문제점들을 발견했음

기존 코드의 문제점:

// ❌ 문제 1: 매번 배열 복사 발생
TArray<EGunType> GetOwnedGunList() const
{
    TArray<EGunType> KeyArray;
    OwnedGuns.GenerateKeyArray(KeyArray);  // 매번 새로운 배열 생성
    return KeyArray;                       // 복사 비용 발생
}

// ❌ 문제 2: null 안전성 부족
void UpdateGunData(AGun* CurrGun)         // nullptr 전달 가능
void SetCurrentGun(AGun* NextGun)         // null 체크 없음

성능 영향 분석:

  • GetOwnedGunList() 호출 시마다 메모리 할당과 복사 발생
  • 총기 교체나 UI 업데이트 시 빈번하게 호출되는 함수
  • 불필요한 CPU 사이클과 메모리 사용량 증가

2. 해결 전략 수립 - 성능과 안전성 동시 개선 💡

핵심 아이디어: "참조 반환 + 캐시 시스템 + 참조 매개변수"

개선 방향 설정:

성능 최적화: TArray 복사 → const 참조 반환 + 캐시
안전성 강화: 포인터 매개변수 → 참조 매개변수

예상 성능 개선:

  • 메모리 할당: O(n) → O(0)
  • 복사 비용: O(n) → O(0)
  • 반복 호출: O(n) → O(1) (캐시 활용)

3. const 참조 반환 시스템 구현 🛠️

반환 타입 변경

// 기존: 값 반환 (복사 비용 발생)
TArray<EGunType> GetOwnedGunList() const

// 개선: const 참조 반환 (복사 비용 제거)
const TArray<EGunType>& GetOwnedGunList()

캐시 시스템 도입

class UGunManager : public UActorComponent
{
protected:
    TMap<EGunType, int32> OwnedGuns;
    
    // 캐시된 총기 리스트 (성능 최적화)
    TArray<EGunType> CachedGunList;
    bool bGunListCacheDirty = true;
    
public:
    const TArray<EGunType>& GetOwnedGunList();
};

4. 스마트 캐시 무효화 로직

캐시 업데이트 전략

const TArray<EGunType>& UGunManager::GetOwnedGunList()
{
    // 캐시가 더티한 경우에만 업데이트
    if (bGunListCacheDirty)
    {
        CachedGunList.Empty();
    	OwnedGuns.GenerateKeyArray(CachedGunList);
    	bGunListCacheDirty = false;
    }
    return CachedGunList;      // 참조 반환 (복사 없음)
}

캐시 무효화 시점

void UGunManager::UpdateGunData(const AGun& CurrGun)
{
    OwnedGuns.Add(CurrGun.GetGunType(), CurrGun.GetCurrentAmmo());
    bGunListCacheDirty = true;  // 데이터 변경 시 캐시 무효화
}

5. 안전성 강화 - 포인터를 참조로 변경 🔒

매개변수 타입 개선

// 기존: 포인터 사용 (null 위험)
void UpdateGunData(AGun* CurrGun)    // nullptr 전달 가능
void SetCurrentGun(AGun* NextGun)    // 매번 null 체크 필요

// 개선: 참조 사용 (컴파일 타임 안전성)
void UpdateGunData(const AGun& CurrGun)  // null 불가능
void SetCurrentGun(AGun& NextGun)        // null 체크 불필요

참조 매개변수의 장점

  • 컴파일 타임 보장: null 값 전달 불가능
  • 런타임 안전성: null 체크 코드 불필요
  • 성능 향상: 포인터 역참조 오버헤드 제거
  • 코드 명확성: 의도가 더 명확해짐

6. 호출부 수정 - 안전한 사용법 적용

PlayerCharacter에서 사용 방식 변경

// 기존: 포인터 직접 전달 (위험)
GunManager->UpdateGunData(CurrWeapon);     // ❌ null 위험

// 개선: null 체크 후 역참조 (안전)
if (CurrWeapon)
{
    GunManager->UpdateGunData(*CurrWeapon); // ✅ 안전한 참조 전달
}

const 참조로 받아서 복사 비용 제거

// 기존: 값으로 받기 (복사 발생)
TArray<EGunType> GunList = GunManager->GetOwnedGunList();  // ❌ 복사

// 개선: const 참조로 받기 (복사 없음)
const TArray<EGunType>& GunList = GunManager->GetOwnedGunList();  // ✅ 참조

7. 성능 벤치마크 및 검증 📊

개선 전후 비교

항목기존 방식개선된 방식개선 효과
메모리 할당매번 발생 (O(n))캐시 활용 (O(1))100% 제거
복사 비용TArray 전체 복사참조 반환100% 제거
null 안전성런타임 체크 필요컴파일 타임 보장완전 보장
코드 복잡성포인터 + null 체크참조 매개변수대폭 감소

실제 사용 시나리오 개선:

  • 총기 교체 시: 매번 배열 생성 → 캐시된 참조 사용
  • UI 업데이트 시: 불필요한 복사 → 직접 참조 접근
  • 에러 방지: null 포인터 위험 → 컴파일 타임 안전성

8. 최종 결과 🎯

달성한 개선사항:

  • 성능 최적화: 불필요한 메모리 할당과 복사 완전 제거
  • 안전성 강화: 참조 매개변수로 null 안전성 확보

📚 개발 참고

C++ 성능 최적화 원칙

1. 불필요한 복사 피하기
   - 값 반환 → const 참조 반환
   - 매개변수 복사 → const 참조 매개변수

2. 캐시 친화적 설계
   - 자주 사용되는 데이터는 캐시해서 재사용
   - 데이터 변경 시에만 캐시 무효화

3. 컴파일 타임 안전성 확보
   - 포인터 → 참조로 null 안전성 보장
   - 타입 시스템 활용한 에러 방지

언리얼 엔진 메모리 관리

// TArray 사용 시 주의사항
TArray<T> GetData() const;           // ❌ 복사 발생
const TArray<T>& GetData() const;    // ✅ 참조 반환

// 매개변수 전달 방식
void ProcessData(TArray<T> Data);    // ❌ 전체 복사
void ProcessData(const TArray<T>& Data); // ✅ 참조 전달

0개의 댓글