에셋 매니저 메모리 최적화 설계


개요

CAssetManager는 텍스처, 스프라이트, 애니메이션, UI, 폰트, 사운드 등 게임 실행에 필요한 다양한 리소스 매니저를 보관하는 싱글톤입니다. 이를 통해 필요한 리소스를 한 곳에서 일관되게 접근하고 관리할 수 있습니다. 본 구조를 설계하며 구상했던 다양한 구현 방식과 그 장단점을 아래에 정리했습니다.

⚠️ 참고: 프레임워크 실행 시, CDataLoader가 CSV 데이터를 읽어 CAssetManager에 포함된 각 매니저들에게 전달하며, 이후 런타임에서 각 매니저가 이를 기반으로 리소스를 관리합니다.

저장소 보기


설계 및 구현 방식 | 자세히 보기

에셋 매니저 설계 과정에서 구현하여 비교한 세 가지 방식을 표와 다이어그램으로 정리했습니다.

구현 방식장점단점
(1) CAssetManager 동적 할당, 모든 리소스 매니저 각각 별도로 동적 할당컴파일 의존성 낮음메모리 단편화 높음
(2) CAssetManager 동적 할당, 모든 리소스 매니저 값 멤버 변수로 선언메모리 단편화 낮음컴파일 의존성 높음
(3) malloc으로 메모리 블록 확보, CAssetManager모든 리소스 매니저placement new로 생성컴파일 의존성 낮음,
메모리 단편화 없음
구현 난이도 증가

장단점을 종합적으로 검토한 결과, 최종적으로 (3)번 방식을 선택했습니다.


코드 분석

template <typename T>
T* PlacementNew(void*& memoryBlock)
{
    T* manager  = new (memoryBlock) T;
    memoryBlock = (char*)memoryBlock + sizeof(T);
    
    return manager;
}

  • PlacementNew<T>()함수는 전달받은 memoryBlock 위치에 placement new를 사용해 T타입 객체를 생성하고, 메모리 블록 포인터를 다음 객체를 배치할 위치로 이동시킵니다.

CAssetManager::CAssetManager(void* memoryBlock)
{
    mTextureManager   = PlacementNew<CTextureManager>(memoryBlock);
    mSpriteManager    = PlacementNew<CSpriteManager>(memoryBlock);
    mAnimationManager = PlacementNew<CAnimationManager>(memoryBlock);
    mUIManager        = PlacementNew<CUIManager>(memoryBlock);
    mFontManager      = PlacementNew<CFontManager>(memoryBlock);
    mSoundManager     = PlacementNew<CSoundManager>(memoryBlock);
}

  • 이 생성자는 malloc으로 확보된 메모리 블록을 받아, PlacementNew<T>()를 사용해 모든 리소스 매니저연속된 메모리 공간에 순서대로 생성합니다.

CAssetManager* CAssetManager::GetInst()
{
    if (!mInst)
    {
        const size_t totalSize = sizeof(CAssetManager)
            + sizeof(CTextureManager)   + sizeof(CSpriteManager)
            + sizeof(CAnimationManager) + sizeof(CUIManager)
            + sizeof(CFontManager)      + sizeof(CSoundManager);
		
        void* memoryBlock = malloc(totalSize);
        mInst = new (memoryBlock) CAssetManager((char*)memoryBlock + sizeof(CAssetManager));
    }
    return mInst;
}

  • 이 함수는 CAssetManager 싱글톤 객체를 반환하며, 최초 호출 시 CAssetManager모든 리소스 매니저에 필요한 메모리를 malloc으로 한 번에 확보한 뒤 placement new로 초기화합니다.

맺는 말

처음에 (1), (2) 방식을 구현했지만 단점이 뚜렷해, 대안을 모색하던 중 두 방식의 장점을 유지하며 단점을 해소한 (3) 방식을 선택했습니다. 구현 난이도가 높아졌지만, malloc으로 단일 메모리 블록을 확보한 뒤 placement new를 사용하는 방식은 메모리 풀 라이브러리 제작 경험이 있어 문제는 없었습니다. 이 방식으로 컴파일 의존성을 최소화하고 메모리 단편화를 방지하며, 동시에 캐시 효율성까지 높여 성능과 메모리 관리 측면에서 균형 잡힌 선택이 되었습니다.