TIL | 팀플 & CS 메모리

Kyu_·2026년 4월 8일

20주차

목록 보기
2/4

EDGameDataSubsystem

핵심 설계 포인트

포인트이유
PhaseHandles를 멤버 변수로 유지TSharedPtr 소멸 시 엔진이 에셋을 언로드할 수 있기 때문
단계별 순차 진행 (콜백 체인)병렬 로드보다 메모리 피크가 낮고 의존성 제어가 쉬움
DataCache의 Key를 FPrimaryAssetId로타입 안전한 검색을 위해 GetData 템플릿과 키 타입을 일치시킴
GetPrimaryAssetIdList 사용등록된 에셋 목록을 Asset Manager가 관리하므로 하드코딩 ID 배열 불필요
UGameInstanceSubsystem 상속레벨 전환에도 살아남아 데이터를 계속 유지할 수 있음

EDAssetManager와 차이

항목UEDAssetManagerUEDGameDataSubsystem
상속 베이스UAssetManager (엔진 싱글톤)UGameInstanceSubsystem (게임 인스턴스 소속)
레이어저수준 인프라 (Infrastructure)고수준 오케스트레이션 (Orchestration)
핵심 질문"이 에셋을 어떻게 메모리에 올리나?""게임에 지금 어떤 데이터가 필요하고, 언제 배포하나?"
관심 대상FSoftObjectPath, FStreamableHandle, I/OEDataLoadPhase, DataCache, 게임 시스템 배포
생존 범위엔진 전체 (에디터 포함)게임 인스턴스 수명과 동일

기능과 동작

항목UEDAssetManagerUEDGameDataSubsystem
동기 로드✅ LoadAssetSync, LoadPrimaryAssetSync❌ 직접 수행 안 함
비동기 로드✅ LoadAssetAsync, LoadPrimaryAssetsAsync✅ 내부에서 UEDAssetManager를 호출
에셋 캐싱❌ 로드 후 포인터 반환만✅ DataCache에 저장, GetData()로 접근
로드 순서 제어❌ 단순 요청만 처리✅ Lobby → Item → Monster 순차 진행
단계 변화 감지✅ OnPhaseChanged 델리게이트
완료 신호❌ 콜백만 제공✅ OnAllDataLoaded 브로드캐스트로 게임 시작 신호
핸들 수명 관리호출자가 직접 관리PhaseHandles TMap으로 일괄 관리

사용시점

상황어느 클래스?
특정 에셋 하나를 즉시 동기 로드UEDAssetManager
인벤토리 아이콘 UI용 비동기 로드UEDAssetManager
게임 시작 시 모든 데이터 일괄 로드UEDGameDataSubsystem
"고블린 스탯이 뭐야?" 데이터 조회UEDGameDataSubsystem::GetData()
로딩 화면 단계 텍스트 업데이트UEDGameDataSubsystem::OnPhaseChanged
로드 완료 후 게임 시작 트리거UEDGameDataSubsystem::OnAllDataLoaded

사용하는 이유

  1. 그러니까 로드 완료 보장의 차이라고 보면된다. 로드 완료가 보장된다. 항상 방어코드적어주지 않아도 됨
  2. 호출할때마다 FPrimaryAssetId를 직접 구성해야 하는데 SyncManager에서 캐시로 관리하면 로드 시점에 이미 검증된 키로 저장되어있어서 조회할때 타임명을 신경안써도 됨
  3. UEDAssetManager를 직접 호출하면 핸들 수명을 호출한쪽에서 직접 관리해야 하는데 SyncManager는 PhaseHandles TMap 하나에서 모든 핸들을 일괄 관리하기 때문에 게임 인스턴스가 살아있는동안 에셋이 절대 언로드 되지 않음
UEDAssetManager 직접UEDGameDataSubsystem 경유
로드 완료 보장❌ nullptr 가능✅ OnAllDataLoaded 이후 항상 유효
FPrimaryAssetId 구성매번 직접로드 시 1회만
핸들 수명호출자가 직접 관리SyncManager가 일괄 관리
방어 코드매번 nullptr 체크 필요불필요

주의사항

UEDSyncManager는 내부적으로 GetPrimaryAssetIdList()로 에셋 목록을 수집하는데 이 함수는 ProjectSettings -> Asset Manager에 등록된 Primary Asset 타입만 조회
메셋, 텍스처같은 리소스는 Primary Asset이 아니기 때문에 목록에 잡히지 않음 얘네들은 에셋번들로 불러와야한다.
Seconday Asset은 Primary Asset이 참조하면 자동으로 로드 된다. 번들이 이에 해당

EDAssetManager는 배달부역할만 함
MonsterData 목록줘, 비동기 로드해줘, 완료되었으니 포인터 줘
SyncManager가 언제, 무엇을, 어떤 순서로 로드할지 결정하고, EDAssetManager는 그 지시를 받아서 실제 I/O 작업만 수행합니다.

시퀀스

Asset Manager에 Primary Asset을 등록해놓음
-> UAssetManager 즉 부모에서 이것을 TMap 형태로 저장해놓음 PrimaryAssetMap으로
-> 이 맵이 언제 채워지냐 StartInitialLoading호출될때 Super::에서 호출되어서 자동으로 채워짐

현재 EDAssetManger는 그냥 커스텀으로 써본것뿐이지 크게 의미를 가지지는 않음
-> 로그를 찍거나, 측정시간을 가지거나 하는식, 나중에 커스텀 우선순위나 블랙리스트 같은거 등록할때는 꽤 유용하게 사용가능하다 확장성을 위한 구조라고 생각하자

EDSyncManager는 역할이 명확
-> 무엇을 어떤 순서로 로드할지 결정
-> 로드 완료된 값을 캐싱 해놓음
-> 데이터 접근 인터페이스를 제공한다.
-> UEDAssetManager에게 로드를 지시하고, 완료된 데이터를 캐시에 보관하고, 게임 코드에 배포하는 데이터 초기화 파이프라인

EDSyncManager -> EDGameDataSubsystem으로 이름 변경

PrimaryAsset은 EDGameDataSubsystem으로 로드하고, UPrimaryDataAsset상속받은 그 클래스에서 secondary asset들은 TSoftObjectPtr로 등록후 번들명을 저장해서 같이 로드할 수 있음


CS

캐시 메모리와 저장 장치 계층 구조

저장장치계층구조 : CPU와 가까울수록 속도가 빠르지만 용량이 작고 가격이 비싸다
캐시메모리 : 속도의 완충지대, CPU가 연산을 아무리 빨리해도 메모리에 데이터를 가져오는 시간이 오래 걸리면 CPU는 멍하니 기다려야 함, 이를 해결하기 위해 CPU와 메모리 사이에 위치한 SRAM 기반의 고속 저장 장치가 캐시 메모리

L1 : 코어와 가장 가깝고 빠름, 보통 코어마다 고유하게 할당
L2 : L1보다 용량은 크지만 조금 더 느림, 코어마다 할당되는 경우가 많음
L3 : 가장 용량이 큼, 여러 코어가 공유하는 형태로 사용

참조 지역성 원리

캐시 메모리는 용량이 작기 때문에 모든 내용을 담을 수 없다. 대신 CPU가 "앞으로 사용할 법한 데이터"를 예측하여 미리 가져다 놓는다. 이 예측의 근거가 되는 것이 바로 참조 지역성의 원리

시간 지역성

최근에 접근했던 메모리 공간에 다시 접근하려는 경향
Tick(float DeltaTime) 함수 내에서 매 프레임 사용하는 DeltaTime 변수나 특정 액터의 참조 변수들은 한 번 사용된 후 곧 다시 사용될 확률이 높다.

공간 지역성

접근한 메모리 공간 근처의 데이터를 다시 접근하려는 경향
TArray에 담긴 데이터들은 메모리상에 쪼르륵 모여있다. 0번 요소에 접근했다면, 다음으로 1번 요소를 사용할 확률이 높으므로 캐시는 주변 데이터를 통째로 쟁반(MBR)에 담아온다.

캐시 히트와 캐시 미스

캐시 히트 : CPU가 필요로 하는 데이터가 캐시 메모리에 딱 들어맞아 즉시 가져오는 경우, 성능이 극대화
캐시 미스 : 예측이 틀려 캐시에 데이터가 없어 메모리(RAM)까지 직접 다녀오는 경우 이때 Cpu는 데이터가 올때까지 기다려야 하므로 성능이 하락

캐시 적중률 계산법
캐시 적중률 = 캐시 히트 횟수 / (캐시히트횟수 + 캐시 미스횟수) => 현대 컴퓨터의 적중률을 85에서 95이상

언리얼 엔진 Insight : 캐시 효율 극대화하는 코딩 전략

언리얼 엔진의 성능 최적화는 어떻게 하면 캐시 미스를 줄일 것인가와의 싸움

  1. TArray사용하기
    TList는 노드들이 메모리 여기저기에 흩어져 있어 공간 지역성이 엉망, CPU는 노드 하나 찾을 때마다 멀리 있는 마트(RAM)를 다녀와야 한다.
    TArray는 데이터가 메모리에 연속적으로 배치, CPU가 0번 데이터를 가져올 때 주변 데이터까지 한꺼번에 캐시로 담아오기 때문에 캐시 히트 확률이 비약적으로 높아진다.

  2. 데이터 기반 설계
    언리얼 엔진 5의 Mass Entity나 나나이트(Nanite)기술의 핵심은 데이터를 캐시가 가장 좋아하는 방식(연속된 배열)으로 정렬하여 하드웨어 성능을 100% 끌어내는데 있다.

정리

  1. 저장 장치는 계층적이다
  2. 캐시는 예측의 예술이다.
  3. 코드 구조가 성능을 결정한다.

많은 초보 개발자가 복잡한 수학 공식에서 최적화를 찾으려고 하지만 시니어들은 메모리 레이아웃에서 답을 찾는다. 데이터가 캐시에 예쁘게 담길 수 있도록 설계하는 것, 그것이 하드웨어를 진정으로 배려하는 엔지니어링의 정수

다양한 보조 기억 장치

HDD

자기장으로 기록하는 기계식 아카이브
하드디스크는 자기적인 방식으로 데이터를 저장하는 자기 디스크의 일종, 물리적인 회전과 바늘의 움직임이 필요한 '기계식' 장치라는 점이 가장 큰 특징

물리적 구성

플래터 : 실질적으로 데이터가 저장되는 동그란 원판, 자성 물질로 덮여있어 N극과 S극을 통해 0과 1을 기록
스핀들 : 플래터는 회전시키는 축, 회전속도는 RPM으로 나타내며, 높을수록 속도가 빠름
헤드 & 디스크 암 : 바늘처럼 생긴 헤드가 플래터 위를 미세하게 떠다니며 데이터를 읽고 씀, 모든 헤드는 암에 부착되어 일제히 이동

논리적인 저장 단위

트랙 : 플래터 상의 동심원 하나를 의미
섹터 : 트랙을 조각낸 하드디스크의 가장 작은 전송 단위, 보통 512B에서 4096B 크기를 가짐
실린더 : 여러 겹의 플래터에서 같은 위치에 있는 트랙들을 연결한 원통 모양의 공간, 헤드를 움직이지 않고 한 번에 읽기 위해 연속된 데이터는 보통 한 실린더에 기록

하드디스크 속도를 결정하는 3요소

  1. 탐색시간 : 헤드를 해당 트랙까지 이동시키는 시간
  2. 회전 지연 : 헤드 밑으로 해당 섹터가 오도록 플래터를 돌리는 시간
  3. 전송 시간 : 하드 디스크와 컴퓨터 간에 실제 데이터를 주고받는 시간

플래시 메모리(SSD)

반도체 기반 초고속 저장 혁명, SSD, USB메모리, SD카드 모두 플래시 메모리 기반, 전기적으로 데이터를 읽고 쓰는 반도체 방식이라 기계적인 소음이 없고 속도가 압도적으로 빠름

셀의 종류와 성능

셀은 데이터를 저장하는 최소 단위, 셀 하나에 몇 비트를 담느냐에 따라 등급이 나뉨

SLC(Single Level Cell) : 셀당 1비트, 가장 빠르고 수명이 길지만 비쌈
MLC(Multi Level Cell) : 셀당 2비트, 속도와 가격의 균형 모델
TLC(Triple Level Cell) : 셀당 3비트 용량이 크고 저렴하여 대중적인 SSD에 쓰임

읽기/쓰기와 삭제의 불일치

플래시 메모리의 가장 독특한 특징은 작업 단위가 다르다.

읽기/쓰기 : 페이지(Page)단위로 수행
삭제 : 페이지보다 큰 블록(Block) 단위로 수행

가비지 컬렉션

플래시 메모리는 덮어쓰기가 불가능 데이터 수정 시 기존 페이지는 Invalid(쓰레기 값)상태가 되고 새로운 페이지에 데이터가 적재됨, 이 쓰레기 값들이 공간을 낭비하는 것을 막기 위해 다음 과정을 거침

  1. 유효한(Valid) 페이지만 새로운 블록으로 복사
  2. 쓰레기 값이 포함되었던 기존 블록을 통째로 삭제

언리얼 Insight : 에셋 스트리밍 최적화

언리얼 엔진5의 혁신적인 기능들은 보조기억장치의 물리적 특성에 깊게 뿌리를 두고 있다.

나나이트와 SSD의 필연적 관계
나나이트는 수억개의 폴리곤을 실시간으로 불러옴, 기계적인 회전이 필요한 HDD의 탐색시간으로는 이 방대한 데이터 흐름을 감당할 수 없음
결과 : 고속 SSD가 있어야만 끊김 없는 고퀄리티 렌더링이 가능하며, 이것이 차세대 콘솔(PS5, Xbox Series X)이 SSD를 필수 장착한 이유

텍스터 팝인(Pop-in)현상의 하드웨어적 원인
HDD환경 : 헤드가 여기저기 흩어진 텍스처 섹터를 찾느라 물리적으로 움직이는 시간(탐색 시간)동안 화면에는 저해상도 텍스처가 머물게 됨
SSD환경 : 탐색 시간이 거의 0에 가깝기 때문에 데이터를 즉각적으로 RAM으로 전송할 수 있어 팝인 현상이 획기적으로 줄어듬

핵심

참조 지역성과 데이터 배치
하드 디스크 성능을 올리려면 연관된 에셋들을 플래터상의 가장 가까운 트랙(실린더)에 모아두는것이 중요 그래야 디스크 암이 덜 움직여서 탐색 시간을 단축할 수 있음, 언리얼 엔진의 빌드 과정에서 데이터패킹이 중요한 이유도 바로 이 하드웨어적 효율성 때문

0개의 댓글