10.29.2014

디버깅과 메모리 최적화

By * 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<> 로 바꾸려 합니다. 그러면 그 레퍼런싱된 블루프린트를 직접 로드하지 않는 한 로드되지 않겠지요.

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

Recent Posts

PixARK Dev Kit으로 만드는 나만의 복셀 월드

에픽게임즈 런처에서 Snail Games가 제공한 모드 제작 툴로 재밌는 PixARK 월드를 만들어보세요.

Drive Studio, 언리얼 엔진으로 Fox Sports의 2018 FIFA 월드컵 방송 제작

Drive Studio는 언리얼 엔진의 강력함을 활용해 FOX Sports 채널의 2018 FIFA 월드컵 중계에서 사용할 배경, 중간...

에픽게임즈, 언리얼 엔진 마켓플레이스의 수익 배분율 88% / 12%로 변경

에픽게임즈는 언리얼 엔진 마켓플레이스의 수익 배분율을 기존의 70% / 30% 에서 88%로 변경하고, 이

...