PART 10 - 강의 6/6
메모리 프로파일링
memreport, LLM, obj list를 활용한 메모리 분석
01
메모리 프로파일링 도구
언리얼 엔진의 메모리 분석 시스템
주요 도구
memreport
상세한 메모리 리포트 생성
LLM (Low Level Memory)
실시간 메모리 추적 및 태깅
obj list
UObject 인스턴스 목록 확인
stat 명령어
실시간 메모리 통계 표시
기본 stat 명령어
Console Commands
// 기본 메모리 통계
stat memory // 전체 메모리 사용량
stat memoryplatform // 플랫폼별 메모리
stat memorystatic // 정적 메모리
// 특정 시스템 메모리
stat levels // 레벨/스트리밍 메모리
stat streaming // 텍스처 스트리밍
stat streamingdetails // 스트리밍 상세
stat particles // 파티클 메모리
// 스크립트/오브젝트
stat script // 블루프린트 메모리
stat scenerendering // 렌더링 메모리
02
memreport 명령어
상세한 메모리 사용량 리포트 생성
memreport 사용법
memreport Commands
// 기본 리포트
memreport
// 전체 상세 리포트
memreport -full
// 파일로 저장
memreport -full -log // Saved/Logs 폴더에 저장
// CSV 형식으로 저장
memreport -full -csv
// 특정 카테고리만
memreport -obj // UObject만
memreport -class // 클래스별 통계
리포트 분석
Sample memreport Output
// memreport -full 출력 예시
Memory Report
===================================
Platform Memory Stats
Total Physical: 16384 MB
Used Physical: 8542 MB
Available Physical: 7842 MB
Process Memory
Working Set: 2845 MB
Virtual Size: 4096 MB
Allocator Stats
Binned2 Allocator
Current: 1245 MB
Peak: 1567 MB
Waste: 42 MB (3.4%)
Asset Memory by Type
Textures: 856 MB
Static Meshes: 234 MB
Skeletal Meshes: 156 MB
Materials: 89 MB
Sounds: 45 MB
Animations: 123 MB
Blueprints: 67 MB
UObject Count by Class
UStaticMesh: 1234 instances, 234 MB
UTexture2D: 5678 instances, 856 MB
AActor: 890 instances, 12 MB
memreport 분석 팁
- 정기적으로 리포트를 저장하여 메모리 추세 파악
- 레벨 전환 전/후로 비교하여 메모리 누수 확인
- 클래스별 인스턴스 수가 예상과 맞는지 확인
- Waste 비율이 높으면 메모리 단편화 의심
03
LLM (Low Level Memory Tracker)
실시간 메모리 추적 및 커스텀 태깅
LLM 활성화
Enabling LLM
// 명령줄 옵션
-llm // LLM 활성화
-llmcsv // CSV 파일 출력
-llmtagsets=Assets // 특정 태그셋만
// 콘솔 명령
stat llm // LLM 통계 표시
stat llmfull // 상세 LLM 통계
stat llmplatform // 플랫폼 LLM
stat llmoverhead // LLM 오버헤드
코드에서 LLM 태깅
LLM Scopes
#include "HAL/LowLevelMemTracker.h"
// 기본 태그 사용
void UMyClass::LoadAssets()
{
LLM_SCOPE(ELLMTag::Assets);
// 이 스코프 내의 모든 할당은 Assets 태그로 추적
MyTextures.Add(LoadObject<UTexture2D>(...));
MyMeshes.Add(LoadObject<UStaticMesh>(...));
}
// 커스텀 태그 정의
LLM_DECLARE_TAG(MyGame);
LLM_DEFINE_TAG(MyGame, TEXT("My Game"), NAME_None, GET_STATFNAME(STAT_MyGameLLM));
void UMyGameSystem::Initialize()
{
LLM_SCOPE_BYNAME(TEXT("MyGame/Initialization"));
// 커스텀 태그로 추적
AllocateResources();
}
// 전역 스코프
class FMyLargeBuffer
{
public:
FMyLargeBuffer()
{
LLM_SCOPE(ELLMTag::Audio);
Data = FMemory::Malloc(1024 * 1024); // 1MB
}
~FMyLargeBuffer()
{
FMemory::Free(Data);
}
private:
void* Data;
};
LLM 태그 카테고리
| 태그 | 설명 |
|---|---|
ELLMTag::Meshes |
스태틱/스켈레탈 메시 |
ELLMTag::Textures |
텍스처 데이터 |
ELLMTag::Audio |
오디오 버퍼 |
ELLMTag::Animation |
애니메이션 데이터 |
ELLMTag::Physics |
물리 시뮬레이션 |
ELLMTag::UObject |
UObject 인스턴스 |
04
obj 명령어로 UObject 분석
UObject 인스턴스 목록 및 참조 분석
obj 명령어
obj Commands
// 모든 UObject 목록
obj list
// 특정 클래스만
obj list class=StaticMesh
obj list class=Texture2D
obj list class=Actor
obj list class=MyCharacter
// 클래스 계층 포함
obj list class=Actor -all // Actor와 모든 자식 클래스
// 정렬
obj list class=Texture2D -sort=size // 크기순 정렬
obj list class=Actor -sort=name // 이름순 정렬
// 개수만 표시
obj count class=Actor
// 가비지 컬렉션
obj gc // GC 강제 실행
참조 분석
Reference Analysis
// 특정 객체의 참조자 확인
obj refs name=MyActor
// 메모리 누수 의심 시
// 1. 레벨 전환 전 obj count 기록
obj count class=MyActor // 예: 10개
// 2. 레벨 전환 후 다시 확인
obj gc // GC 강제 실행
obj count class=MyActor // 여전히 10개면 누수 의심!
// 3. 참조 체인 확인
obj refs class=MyActor
코드에서 UObject 분석
Programmatic Analysis
// 특정 클래스의 인스턴스 수 확인
void UMyDebugHelper::PrintObjectCounts()
{
int32 ActorCount = 0;
int32 ComponentCount = 0;
for (TObjectIterator<AActor> It; It; ++It)
{
ActorCount++;
}
for (TObjectIterator<UActorComponent> It; It; ++It)
{
ComponentCount++;
}
UE_LOG(LogTemp, Log, TEXT("Actors: %d, Components: %d"),
ActorCount, ComponentCount);
}
// 특정 조건의 객체 찾기
void UMyDebugHelper::FindLeakedActors()
{
for (TObjectIterator<AActor> It; It; ++It)
{
AActor* Actor = *It;
// Pending Kill이 아닌데 World가 없는 경우 = 누수 가능성
if (!Actor->IsPendingKillPending() && !Actor->GetWorld())
{
UE_LOG(LogTemp, Warning, TEXT("Potential leak: %s"),
*Actor->GetName());
}
}
}
// 메모리 크기 확인
void UMyDebugHelper::PrintMemorySize(UObject* Object)
{
FArchiveCountMem MemCount(Object);
int64 Size = MemCount.GetNum();
UE_LOG(LogTemp, Log, TEXT("%s: %lld bytes"),
*Object->GetName(), Size);
}
05
메모리 최적화 전략
프로파일링 결과를 바탕으로 한 최적화
일반적인 메모리 문제와 해결
텍스처 메모리 과다
- LOD Bias 조정
- Virtual Texture 사용
- 텍스처 압축 설정
메시 메모리 과다
- Nanite 활성화
- LOD 설정 최적화
- 인스턴싱 활용
UObject 누수
- UPROPERTY 누락 확인
- 참조 체인 분석
- 명시적 Destroy 호출
동적 할당 과다
- Object Pooling
- TArray Reserve
- 메모리 재사용
코드 레벨 최적화
Memory Optimization Patterns
// 1. TArray 메모리 사전 할당
TArray<FVector> Points;
Points.Reserve(1000); // 재할당 방지
// 2. TSparseArray 사용 (삭제가 빈번한 경우)
TSparseArray<FMyStruct> SparseData;
// 3. 불필요한 복사 방지
void ProcessData(const TArray<FVector>& Data) // const 참조!
{
for (const FVector& Point : Data) // const 참조!
{
// ...
}
}
// 4. 스마트 포인터로 공유
TSharedPtr<FLargeData> SharedData = MakeShared<FLargeData>();
// 여러 곳에서 공유, 마지막 참조 해제 시 자동 삭제
// 5. Soft Reference로 필요 시 로드
UPROPERTY()
TSoftObjectPtr<UTexture2D> LazyTexture;
void LoadWhenNeeded()
{
if (!LazyTexture.IsValid())
{
LazyTexture.LoadSynchronous();
}
}
// 6. 객체 풀링
UCLASS()
class UProjectilePool : public UObject
{
TArray<AProjectile*> AvailableProjectiles;
public:
AProjectile* GetProjectile()
{
if (AvailableProjectiles.Num() > 0)
{
return AvailableProjectiles.Pop();
}
return GetWorld()->SpawnActor<AProjectile>();
}
void ReturnProjectile(AProjectile* Projectile)
{
Projectile->Reset();
AvailableProjectiles.Add(Projectile);
}
};
메모리 버짓 모니터링
Memory Budget Check
// 메모리 버짓 확인 유틸리티
void UMemoryMonitor::CheckMemoryBudget()
{
FPlatformMemoryStats MemStats = FPlatformMemory::GetStats();
const int64 UsedMB = MemStats.UsedPhysical / (1024 * 1024);
const int64 TotalMB = MemStats.TotalPhysical / (1024 * 1024);
const int64 BudgetMB = 4096; // 4GB 버짓
UE_LOG(LogTemp, Log,
TEXT("Memory: %lld / %lld MB (Budget: %lld MB)"),
UsedMB, TotalMB, BudgetMB);
if (UsedMB > BudgetMB * 0.9)
{
UE_LOG(LogTemp, Warning,
TEXT("Memory usage at 90%% of budget!"));
// 자동 정리 트리거
GEngine->ForceGarbageCollection(true);
}
}
메모리 프로파일링 워크플로우
- 베이스라인 측정: 게임 시작 시 memreport -full
- 주기적 모니터링: 레벨 전환, 주요 이벤트 시 측정
- 비교 분석: 리포트 간 차이 분석
- 누수 탐지: obj list로 예상치 못한 인스턴스 확인
- 최적화 적용: 가장 큰 영향을 주는 항목부터 개선
SUMMARY
핵심 요약
- stat memory, memreport로 전체 메모리 사용량 분석
- LLM으로 카테고리별 실시간 메모리 추적 및 커스텀 태깅
- obj list, obj count로 UObject 인스턴스 분석
- obj refs로 참조 체인 분석하여 메모리 누수 탐지
- TArray Reserve, Object Pooling, Soft Reference로 메모리 최적화
Part 10 완료
Part 10 테스트 및 디버깅 파트를 완료했습니다. Part 11에서는 UE 5.6/5.7의 최신 기능들을 학습합니다.
PRACTICE
도전 과제
배운 내용을 직접 실습해보세요
실습 1: LLM 태그 기반 메모리 추적
LLM(Low Level Memory Tracker)을 활성화하고, stat LLM 명령으로 카테고리별 메모리 사용량을 확인하세요. RPG 프로젝트에서 Mesh, Texture, Audio, Animation의 메모리 비율을 분석하세요.
실습 2: 메모리 누수 탐지
MemReport 명령으로 메모리 스냅샷을 찍고, 시간 경과에 따른 메모리 증가 패턴을 분석하세요. 던전 입장/퇴장을 반복하며 메모리가 완전히 해제되는지 확인하세요.
심화 과제: 플랫폼별 메모리 예산 관리
타겟 플랫폼(PC/콘솔)의 메모리 예산을 설정하고, memreport -full 결과를 기반으로 예산 초과 항목을 최적화하세요. 텍스처 해상도 하향, 미사용 에셋 제거, LOD 강화 등의 전략을 수립하세요.