Android 게임 개발에서 메모리 사용은 매우 중요합니다. Unreal Engine을 사용하여 게임을 개발할 때는 이러한 메모리 제약 조건을 충족하고 메모리 누수 및 과도한 메모리 할당을 방지하기 위해 메모리 상태를 지속적으로 분석하고 추적해야 합니다.
셰이더 순열 감소를 통한 메모리 절약
Lighting 또는 Mobile Shader Permutation
Reduction에서 사용하지 않는 옵션을 삭제하면 셰이더 순열 수를 최소화할 수 있습니다. 이렇게 하면 앱 패키징 크기가 크게 줄어들고 파이프라인 상태 객체 (PSO) 수가 줄어들며 사용하지 않는 렌더 타겟 또는 더미 텍스처가 생성되지 않아 런타임 메모리가 줄어들고 성능이 향상됩니다.
자세한 내용은 휴대기기용 기타 조명 및 셰이더 순열 감소 설정 섹션을 휴대기기 성능 가이드에서 참고하세요.
애셋 로드 최적화
하드 레퍼런스를 사용하여 애셋을 로드하면 게임에서 사용되지 않는 연결된 콘텐츠도 동시에 메모리에 로드되어 불필요한 메모리 소비가 발생합니다. 특히 블루프린트에서 상속이 잘못 사용되면 상위 요소의 불필요한 CDO (클래스 기본 객체)가 할당되어 상당한 양의 메모리가 낭비됩니다. 이러한 경우 다음 기법을 적용하여 메모리 사용을 최적화하세요.
- C++ 코드 정의 및 데이터 분리: C++ 코드에서 핵심 기능을 정의하고 실제 데이터와 분리합니다.
사용
UCLASS(Abstract) - 상속 최소화: 리프 노드 블루프린트에서만 데이터 설정 및 애셋 로드를 실행합니다.
- 간접 레퍼런스 사용: 간접 속성 레퍼런스를 사용하여
TSoftObjectPtr하드 레퍼런스를 피하고 런타임에 필요한 경우에만 비동기적으로 애셋을 로드합니다. - 주의사항: 비동기 애셋 로드를 사용할 때는 로드 완료 콜백 중에 유효성 검사
(
IsValid)를 실행하여 매달린 포인터 또는 비정상 종료를 방지해야 합니다.
자세한 내용은 애셋 참조 및 모바일 게임 빌드 UE5 사용을 참고하세요.
스마트 포인터 및 가비지 컬렉션 (GC) 조정
C++ 개발 중에 스마트 포인터 (TSharedPtr, TWeakPtr 등)를 사용하면 매달린 포인터와 메모리 누수를 방지할 수 있습니다. 또한 TWeakPtr을 사용하면 객체에 대한 강력한 레퍼런스가 생성되지 않으므로 가비지 컬렉터가 객체를 수집할 때 레퍼런스 수를 늘리지 않습니다. 이렇게 하면 불필요한 객체 생존이 방지되고 GC가 트래버스해야 하는 레퍼런스 그래프의 복잡성이 낮아져 GC 오버헤드가 크게 줄어듭니다. 가비지 컬렉션은 레벨 전환 또는 사용자 상호작용이 없는 경우와 같은 비활성 기간 중에 ForceGarbageCollection을 트리거하여 모바일 환경에서 효과적으로 관리할 수 있습니다. 또한 게임이 인식 가능한 상태이거나 백그라운드에 있을 때 (예: onStop 콜백) 트리거하여 활성 게임플레이 환경에 영향을 주지 않고 가비지 컬렉션을 실행할 수 있습니다.
자세한 내용은 가비지 컬렉션 및 스마트 포인터를 참고하세요.
재배치 테이블 압축을 사용한 최적화
재배치 테이블 압축은 ELF 바이너리의 재배치 테이블 크기를 압축하여 Android 바이너리 크기를 줄이는 최적화 방법입니다. Android minSDKVersion이 23 이상이면 APS 재배치 테이블 압축이 지원됩니다. minSDKVersion이 28 이상이면 RELR 재배치 테이블 압축이 추가로 지원되어 .so 파일의 크기가 크게 줄어듭니다. 이렇게 하면 다운로드 크기가 줄어들 뿐만 아니라 런타임에 .so mmap의 메모리 사용 공간이 줄어들어 전반적인 실제 메모리 압력이 완화됩니다.
AndroidToolChain.cs
if (MinSDKVersion >= 28)
{
Result += " -Wl,--pack-dyn-relocs=android+relr,--use-android-relr-tags";
}
else if (MinSDKVersion >= 23)
{
Result += " -Wl,--pack-dyn-relocs=android";
}
이 최적화를 적용하려면 프로젝트 설정 또는 bEnableAdvancedBinaryCompression 변수에서 재배치 테이블 압축 사용 이 사용 설정되어 있는지 확인하세요.
바이너리에 RELR 압축이 올바르게 적용되었는지 확인하려면 Android NDK의 llvm-readelf 도구를 사용하세요.
llvm-readelf -d libUnreal.so | grep RELR
실제로 Unreal Engine의 ThirdPerson 템플릿을 빌드하고 dumpsys meminfo를 사용하여 측정하면 .so mmap의 PSS 및 RSS 사용량이 줄어드는 것을 확인할 수 있습니다.
| 카테고리 | PSS 총계 | Private Dirty | Private Clean | Swap Dirty | RSS 총계 |
|---|---|---|---|---|---|
| .so mmap 압축 전 | 170,210 | 24,360 | 141,564 | 0 | 242,204 |
| .so mmap 압축 후 | 138,031 | 24,748 | 108,976 | 0 | 210,140 |
텍스처 압축
적절한 텍스처 압축을 사용하여 전반적인 그래픽 리소스의 메모리 사용 공간을 줄이세요. 모바일 표준인 ASTC (Adaptive Scalable Texture Compression)를 사용하면 동일한 크기에서 ETC2보다 우수한 시각적 품질을 제공하거나 동일한 시각적 품질 목표에서 더 낮은 메모리 소비로 더 높은 압축률을 달성할 수 있습니다.
텍스처 스트리밍 최적화
모바일 환경에서 기본 Unreal Engine 텍스처 스트리밍 풀 크기를 직접 사용하면 기기 사양에 비해 지나치게 큰 메모리 풀 이 미리 할당될 수 있습니다. 따라서 모바일 기기 프로필을 정의하고 기기 메모리 한도에 따라 적절한 텍스처 스트리밍 풀 크기를 선택하는 것이 좋습니다. 또한 에디터의 통계 기능을 사용하여 텍스처 통계를 확인하고 런타임에 극단적인 해상도를 차지하는 메모리 소비가 많은 텍스처를 식별하세요. 이를 필수 텍스처 해상도 기능과 페어링하여 리소스 사용량을 할당된 텍스처 메모리 버퍼 내에서 제어하면서 까다로운 텍스처의 한도를 적용하거나 최대 해상도를 줄이세요.