2014. 10. 29.

디버깅과 메모리 최적화

글쓴이 * Ben Zeigler

게임 개발이 특정 단계에 이르면, 정확히 무엇을 왜 메모리에 로드하는지 알아내는 것이 매우 중요해 집니다. 새로운 애셋들 제작에 따라 게임의 덩치는 커져만 가고, 로드 시간은 길어지면서 메모리 부족 문제에 시달리기 마련입니다. 다행히도 UE4 에는 메모리에 뭐가 왜 있는지 추척하기 위한 유용한 툴이 내장되어 있습니다. 저는 여기 에픽에서 이 툴을 사용하여 포트나이트의 메모리 사용량과 로드 시간을 최적화시키고 있습니다.

Memreport 로 문제 찾기

첫 단계는 항상 memreport 명령입니다. 사용하려면 ` 키로 콘솔을 열고 "memreport" 를 치면 간단한 보고를, "memreport -full" 를 치면 보다 상세한 보고를 받을 수 있습니다. 그런 다음 YourGame/Saved/Profiling/MemReports 폴더에 들어가서 맵 이름과 시간표 태그가 찍힌 .memreport 파일을 찾아봅니다. 이 파일은 간단한 텍스트 파일로, BaseEngine.ini 의 [MemReportCommands] (, -full 의 경우 [MemReportFullCommands]) 섹션 안에서 모든 명령을 실행한 출력 결과가 포함되어 있습니다. 원한다면 자신의 engine.ini 파일에서 게임에 실행하고자 하는 명령을 바꿀 수 있지만, 기본 명령으로도 간단히 시작해 보기에 좋습니다.

memreport 파일의 첫 부분은 전체적인 메모리 사용량과 함께, 플랫폼별 메모리 사용량과 모든 등록된 메모리 통계에 대한 섹션이 있습니다. "STAT_PixelShaderMemory" 같은 메모리 통계 이름에 대한 소스를 검색해 보면 정확히 어떤 것들이 기여하는지 확인할 수 있습니다. 다음 섹션은 "obj list -alphasort" 명령의 출력 결과로, 모든 UObject 클래스와 그 메모리 사용량을 나열해 줍니다. 그 이후에는 렌더링 메모리, 로드된 스트리밍 레벨 상태, 스폰된 액터에 대한 섹션이 따릅니다. -full 명령 역시 스태틱 메시나 텍스처같은 개별 애셋에 대한 섹션이 있어, 비싼 애셋을 찾아보는 데 유용합니다.

"obj list" 의 출력은 약간의 설명이 필요합니다. 포트나이트 리포트에서 몇 줄 살펴보겠습니다:

Class

Count

NumKBytes

MaxKBytes

ResKBytes

ExclusiveResKBytes

AIPerceptionSystem  

1

0K

0K

0K

0K

AnimSequence

19

1018K

986K

986K

986K

Material

263

767K

800K

888892K

1080K

memreport 를 사용하면 메모리 상황이 어떤지 빠르게 확인할 수 있습니다. memreport 를 전후로 두 번 찍어 텍스트 diff 툴로 비교해 보는 것도 매우 좋습니다. 쉽게 diff 가능하도록 소팅되어 있습니다. 포트나이트의 예제를 통해 엔진 툴을 사용하여 특정 애셋이 로드된 이유를 알아내는 방법을 보여드리겠습니다. 먼저 "memreport -full" 출력을 검색하여 뭔가 잘못된 듯한 커다란 애셋을 찾는 것으로 문제를 확인합니다. memreport 의 이 줄 버전에서: 첫 열은 클래스 이름, 그 뒤에 해당 클래스의 인스턴스 수 입니다. 메모리 열의 경우, 처음과 마지막 열이 중요한데, 해당 클래스의 모든 인스턴스에 대해 누적된 메모리입니다. NumKBytes 는 메모리에서 UObject 의 바디에 사용되는 메모리 양인 반면, ExclusiveResKBytes 는 사운드 버퍼처럼 해당 UObject 에 전적으로 소유된 UObject 가 아닌 "리소스"에 사용되는 메모리 양입니다. UObject::GetResourceSize 는 오브젝트에 대한 리소스 크기를 결정하는 함수입니다. ResKBytes 는 공유 리소스가 포함되어있어 여기선 그다지 유용하지 않은데, 머티리얼의 경우 ResKBytes 총합은 머티리얼당 동일한 공유 텍스처가 한 번씩 포함되어 있어 인위적으로 늘어나 있습니다. 즉 특정 클래스에 사용되는 메모리 양을 확인하기 위해서는, 첫 번째와 네 번째 열의 값을 더해주면 됩니다.

StaticMesh .../S_Hex_Urban_Standard_03.S_Hex_Urban_Standard_03  1K 1K 14306K 3220K

메모리를 3MB 가득 차지하고 있는 스태틱 메시가 하나 있는데, 게임 해당 부분 플레이 당시에는 필요치 않아서 로드될 것으로 기대하지 않았던 메시입니다. 무언가가 이 애셋을 레퍼런싱하는 통에 필요치 않은데도 로드하고 있는 것입니다. 왜 그런지 알아봐야 겠습니다.

잘못된 레퍼런스를 추적하기에 좋은 기법은 두 가지 있습니다. 먼저 에디터를 로드한 다음 문제가 되는 애셋을 콘텐츠 브라우저에서 찾아봅니다. 그 후 거기에 우클릭하고 레퍼런스 뷰어를 선택하면, 다음과 같은 화면이 뜹니다:

Reference Viewer

레퍼런스 뷰어를 통해 레퍼런스를 탐색하며 오브젝트를 레퍼런싱하는 것이 무엇인지 빠르게 확인할 수 있습니다. 이 경우 블루프린트가 레퍼런싱하고 있는데, 그 블루프린트를 더블클릭해 보면 어디서 레퍼런싱하는지 확인할 수 있습니다. 이 방법으로 그 비싼 메시를 무엇이 로드하고 있는지 용의자를 몇 빠르게 잡을 수 있습니다.

다음에 사용할 툴은 obj refs 명령입니다. 프로파일을 찍었던 게임 인스턴스 안에서, 콘솔에 "obj refs name= S_Hex_Urban_Standard_03 shortest" 명령을 내립니다. 몇 초 후 GC 루트에서 타겟 오브젝트까지의 레퍼런스 체인에 대한 덤프가 로그에 출력됩니다.이 예제에 대한 샘플 출력은 이렇습니다:

(root) World /Game/Maps/Zones/Zone_Temperate_Urban.TheWorld->CurrentLevel

Level .../Zone_Temperate_Urban.TheWorld:PersistentLevel->ULevel::AddReferencedObjects() 

FortWorldManager .../PersistentLevel.FortWorldManager_0->CurrentWorldRecord

FortWorldRecord .../PersistentLevel.FortWorldManager_0.FortWorldRecord_1->ZoneTheme

(standalone) FortZoneTheme .../ZoneTheme_Urban.ZoneTheme_Urban->HexTileClass

BlueprintGeneratedClass .../HexTile_Urban01.HexTile_Urban01_C->UClass::AddReferencedObjects()

HexTile_Urban01_C .../HexTile_Urban01.Default__HexTile_Urban01_C->Hex Deco Meshes

(target) StaticMesh .../S_Hex_Urban_Standard_03.S_Hex_Urban_Standard_0

이 출력은 절대 GC 되지 않은 루트 오브젝트에서 타겟 오브젝트까지의 레퍼런스 체인입니다. 공간 절약을 위해 경로를 단축시켰습니다. 출력 각 줄에 대해 (root) 같은 옵션 노트로 시작한 다음, 오브젝트의 클래스가 오고, 오브젝트의 전체 경로에 이어 마지막으로 해당 오브젝트의 어떤 측면에 레퍼런스가 저장되는지 나옵니다. 이 예제에서 일부 레퍼런스는 커스텀 AddReferencedObjects() 함수에 저장되어 있는 반면, 다른 것들은 편집가능 UObject 프로퍼티에 저장되어 있습니다.

그 출력을 위에서 아래로 읽어내려가다 보니, 블루프린트가 FortZoneTheme 에 레퍼런싱되어 있고, 차례로 그것은 포트나이트의 세이브 게임 시스템의 일부에 레퍼런싱되어 있음을 확인할 수 있습니다. 특히나 이 경우 FortZoneTheme 의 HexTileClass 프로퍼티를 TSubclassOf<> 에서 TAssetSubclassOf<> 로 바꾸려 합니다. 그러면 그 레퍼런싱된 블루프린트를 직접 로드하지 않는 한 로드되지 않겠지요.

이는 가능한 해법 중 한 가지일 뿐이지만, 제 경험상 불필요한 애셋을 로드하지 않도록 하는 것이 메모리를 절약하고 로드 시간을 단축시키는 데 가장 쉽고 효과적인 방법이었습니다. 메모리 최적화에 대해서라면 할 말은 많습니다. 질문 있으신 경우 영문 포럼 또는 네이버 카페에 해 주시기 바랍니다.

최근 게시글

2018년 언리얼 엔진 기대작

주요 매체들에서는 새해가 시작되면서 2018년 최고의 기대작들을 꼽고 있으며, 이 기대작 목록에는 언리얼 엔진 타이틀이 가득 들어있습니...

2017년 12월 NVIDIA Edge 프로그램 수상자

언리얼 커뮤니티가 다시 한번 나섰습니다. 이번에 NVIDIA GTX 1080Ti를 타게 된 놀라운 프로젝트들을 확인해 보시고, 여러분도...

LA에서 열리는 언리얼 엔진 VFX 마스터클래스

에픽게임즈의 언리얼 엔진 전문가들이 nomon VFX 학원에서 리얼타임 퍼포먼스를 주도하는 기본 개념과 워크플로우에 대한 두 가지 마스...