UDN
Search public documentation:

MemoryProfilingHomeKR
English Translation
日本語訳
中国翻译

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 홈 > 퍼포먼스, 프로파일링과 최적화 > 메모리 사용량과 프로파일링

메모리 사용량과 프로파일링


문서 변경내역: Jeff Wilson 작성. 홍성진 번역.

개요


메모리 사용량은 항상 비디오 게임의 최우선 관심사인데, 메모리가 제한된 콘솔이나 모바일 디바이스에는 특히나 더합니다. 이는 디스크 상의 콘텐츠 애셋이 차지하는 공간의 양에서부터 런타임 도중 각기 다른 시스템의 메모리 사용량까지 확대됩니다. 이들 모두 메모리 사용량을 적정선 안에 맞추는 데 있어 극도로 중요한 필수 정보입니다.

메모리 디버깅: 단계별


1. 게임 로드

메모리 부족으로 게임을 로드할 수 없다면, Level Optimization KR 페이지에서 레벨 최적화 방법을 참고해 보시기 바랍니다.

일반적으로 메모리 예산 지침을 세우는 것이 좋은데, 예를 들어 한 레벨에 스태틱 메시는 몇 개나 사용할 수 있는가, 아니면 스켈레탈 메시, 애니메이션, 사운드 등의 애셋은 어떠한가 등입니다. 이는 게임의 역할과 내용에 따라 극도로 달라질 수 있습니다.

2. 게임을 실행하고 관찰

게임이 실행되었다 해도 메모리 부족 걱정을 푹 놓을 수 있는 것은 아닙니다. 게임이 메모리를 얼마나 사용하는지, 새 콘텐츠와 기능에 쓸 메모리가 얼마나 되는지를 알아야 합니다.

3. 메모리 사용량 감소 (OOM 없이)

메모리가 부족할 때, 왜 그런지 범위를 줄여볼 필요가 있습니다.

가능한 원인은:

  • 스태틱 메시가 너무 많은 레벨.
  • 프로젝타일과 파티클을 너무 많이 만들어 내는 AI.
  • 메모리 할당이 과한 코드.

기본적인 이해

메모리를 어디에 얼마나 써야 하는지 정답은 없습니다.

게임에 따라 다르며, 게임에 집중된 부분에 따라 다릅니다.

그러므로 현재 사용량이 어떻게 되는지, 예산은 어때야 하는지 알아야 합니다.

  • 예산: 메모리를 뭐에 쓸 것인가.
  • 모든 애셋이 최적화되어 불필요한 리퍼런스가 없는지 확인.
  • 게임 실행을 유지하기에 (단편화용) 버퍼가 충분한지 확인.

메모리 트래킹 툴과 기술


STAT LEVELS

STAT LEVELS 명령은 메모리에 로드된 레벨이 몇이나 되는지 확인할 때 사용합니다.

  • Red - 레벨이 로드되어 표시중입니다.
  • Orange - 레벨이 표시되려는 프로세스에 있습니다.
  • Yellow - 레벨이 로드되었으니 표시되지 않습니다.
  • Blue - 레벨이 언로드되었으나 여전히 메모리에 있으며, 가비지 컬렉션 발생시 정리됩니다.
  • Green - 레벨이 언로드되었습니다.
  • Purple - 레벨이 로드전(preloading)입니다.

로드할 필요가 없는 레벨이 있는 경우, 로드하지 마십시오. 노랑 레벨이 로드하지 않거나 가능하면 유예(defer)시키기 좋은 후보들입니다. 큰 스트리밍 레벨이 있는 경우, 분할이나 최적화 시도해 보시기 바랍니다.

STAT MEMORY

STAT MEMORY 명령은 레벨의 애셋 메모리 사용량의 기본적인 개요를 제공합니다.

  • Audio Memory 오디오 메모리
  • Novodex Allocation 노보덱스 할당
  • Animation 애니메이션
  • Vertex Lighting 버텍스 라이팅
  • StaticMesh Vertex/Index 스태틱 메시 버텍스/인덱스
  • SkeletalMesh Vertex/Index 스켈레탈 메시 버텍스/인덱스
  • Decal Vertex/Index 데칼 버텍스/인덱스
  • VertexShader 버텍스 셰이더
  • PixelShader 픽셀 셰이더
  • Texture Pool Size 텍스처 풀 크기
  • FaceFX

언리얼 리퍼런스 시스템 때문에 애니메이션, 스켈레탈 메시, 오디오, FaceFX 중 다수는 로드되는 게임 콘텐츠 데이터 크기와 관련이 있습니다. 이 문서 추후에 부연 설명하겠습니다.

stat_memory.jpg

Content Comparison


콘텐츠가 많을 때는 게임에 사용되는 오브젝트의 메모리 사용량이 엄청나게 달라질 수 있습니다. 이 엄청난 차이는 보통 여러 사람이 애셋을 만들면서 생긴 부작용입니다. 또는 "보스전" 상황을 그냥 중간보스 상황으로 바꾼 경우라든지요. 시간에 따라 변하기 마련입니다. 애셋을 추적해서 비교해 볼 수가 없다면 자원을 제대로 활용하지 못하게 됩니다.

기본적으로, 동일한 종류의 무기는 동일한 비용이 들어야 정상입니다. 동일한 종류의 캐릭터도 마찬가지고요. 그래야만 전부 일일히 대조하지 않고도 쉽게 레벨을 짜 나갈 수 있는 것입니다.

게임명 ContentComparisonCommandlet

주: 이 작업은 야밤에 돌리는 것이 좋습니다.

애셋의 전체 메모리를 분석하여 비교하기 좋게 다수의 "주요" 범주로 나눠 줍니다.

이들은 현재 BaseEditor.ini 에 위치해 있으며, 다음과 같습니다:

[ContentComparisonReferenceTypes]
+Class=AnimSet
+Class=SkeletalMesh
+Class=SoundCue
+Class=StaticMesh
+Class=Texture2D

이 속성을 사용하는 이유는 보통 메모리 사용량의 대다수를 대표해 주는 데다가, 애셋 간의 비교가 가장 쉽기 때문이기도 합니다.

예로 N 무기가 있다 칩시다. 작은, 중간, 큰 무기로 나눠볼 수 있겠죠. 일반적으로 작은 무기가 큰 무기보다 비싸지는 말아야 될 겁니다. 그래서 각 무기의 비용을 시각화시켜 볼 수 있으면 비용이 맞는지 쉽게 판단해 볼 수 있습니다.

좀 더 일반적으로, 애셋 종류에 따른 표준 편차를 낮추는 게 좋을겁니다. 예로, 비슷한 캐릭터가 N개 있다면, 스켈레탈 메시 측면에서 다른 캐릭터보다 한 캐릭터의 비용이 1.5배에서 2배 정도까지 차이가 나게 되면 좋지 않을 겁니다.

한 캐릭터 비용이 다른 것보다 쎄지는 이유는 여럿 있습니다만, 그 중 일부는 단순히 무지에서 비롯된 겁니다. 이 커맨드릿의 아이디어는 비용을 목록화시켜서 기대치에 맞아 떨어지는지 확인해 보자는 것입니다. 분리물(outlier)을 조사해 보는 데도 좋겠습니다.

ContentComparisionCommandlet 이 살펴볼 클래스를 설정해 주려면, DefaultEditor.ini 의 ContentComparisonClassesToCompare 부분을 수정하십시오.

콘텐츠 막대그래프

어떻게 15메가 사운드가 항상 로드된다는걸 알게 되었습니다. 근데 어떤 사운드들인지? "listsounds" 를 해 보면 사운드가 수백개씩 떠서, 어느건지 알아보기가 정말 힘듭니다. 로드된 텍스처가 수백개쯤 될 때는 텍스처도 마찬가지겠습니다.

메모리에 어떤 종류 의 사운드가 있는지 고수준으로 표시해 주면 시간 절약을 많이 할 수 있을 겁니다. 퍼센트만 맞으면, 좋은게 좋은겁니다. 근데 사운드 예산의 40%가 발소리에 쓰이고 있는걸 확인했다! 바로 최적화 시작입니다!

MemLeakCheck 와 xls 스프레드 시트를 조합해서 막대그래프를 만듭니다. 사운드와 텍스처에 사용할 수 있는 샘플 .xls 파일입니다: SoundTextureHistograms.xlsx

레벨을 로드하고서 해 줄 작업은:

  • MemLeakCheck
  • .xls의 SoundData 탭에 Sounds 목록을 복사해다 붙입니다.
  • .xls의 TextureData 탭에 Textures 목록을 복사해다 붙입니다.
  • .xls가 범주를 나눠 해당 범주별로 메모리 사용량을 나타내 줍니다.

주: SoundClass나 TextureGroup을 추가했다면 해당 범주에 맞게끔 .xls 파일을 업데이트해 줘야 합니다.

엔진 메모리 풀


엔진이 새 레벨을 로드하고 오브젝트를 새로 만들면서, 시간에 따라 커지게 되는 풀/캐시/목록이 여럿 있습니다. 여러 레벨 로드 이후 안정화되게 됩니다. 문제는 전부 다 제대로 "제한"(cap)되거나 "정리"(clear)되지 않는다는 겁니다. 그래서 보통의 "stat memory"형 로깅에는 보이지 않는 높은 워터마크가 나오게 됩니다. 이들 중 일부는 WorldInfo.ClearObjectPools 나 풀별 클리어 함수를 호출하여 비워집니다.

풀/캐시/목록 예제입니다:

  • Name Table
  • Octree
  • RB_BodyInstance Pool (c.f. DISABLE_POOLED_RB_INSTANCES)
  • RB_ConstraintInstance Pool (c.f. DISABLE_POOLED_RB_INSTANCES)
  • Static Mesh Collision Scale
  • Render Thread 상의 Static Draw 리스트
  • EmitterPool 을 사용중이라면 Particles
  • DecalManager 를 사용중이라면 Decals

핫 스팟 보고서


핫 스팟 보고서는 서브레벨 스트림 인/아웃에 따른 메모리 급락을 알아내기에 좋습니다. 렙디가 메모리를 가장 많이 쓰는 버킷을 알아보기 쉽게 해 주며, 레벨이 메모리에 더 잘 들어맞도록 할 수도 있을 것입니다. 핫 스팟 리포트 제작 및 사용 안내서는 Hot Spot Report Generation KR 페이지를 참고해 보시기 바랍니다.

MemLeakCheck 트래킹


MemLeakCheck 는 게임내에서 메모리가 어디에 쓰이고 있는지에 대한 것을 이해 및 사용하기 쉽게 제공해 줍니다. 그냥 텍스트파일이라 diff해 보면 시간에 따른 변화를 쉽게 알아볼 수 있습니다.

이 기법에 대한 세부사항은 MemLeakCheck Tracking KR 페이지를 참고하시기 바랍니다.

텍스처 풀


텍스처 풀 크기는 고정이라서 텍스처 양이 엄청나다 할지라도 전체 시스템 메모리에 악영향을 끼치지는 않을 겁니다. 그래도 그 메모리를 효과적으로 사용중인지는 확인해 봐야 겠습니다. 즉 우리가 눈으로 보는 텍스처 메모리의 양이 풀 크기 이내인지 확인해야 한다는 소립니다. 우리 주변 맵이 텍스처 풀 크기를 넘어서지만 않으면야 전 맵에 걸쳐 텍스처가 억만개 있대도 괜찮습니다.

stat streaming 명령으로 활용할 텍스처 스트리밍 통계를 확인해 볼 수 있습니다. 눈여겨 볼 통계 부분은:

  • Streaming Fudge Factor: 스트리밍 퍼지(임시) 팩터, 가급적 1.0f 에 가까울 수록 좋습니다.
  • Over Budget: 예산 초과, 가급적 0.0f 에 가까울 수록 좋습니다.

listtextures 콘솔 명령 도 있습니다. 현재 풀에 있는 텍스처 전부의 크기와 이름을 전부 표시해 줍니다. 목록을 살펴보면 풀에 문제있는 텍스처가 있는지 알아볼 수 있을겁니다!

에디터에 TextureStats 텍스처통계 탭이 있습니다. 레벨을 PIE로(에디터에서) 돌려보고 나면 요 탭에 텍스처 관련 통계가 여럿 뜹니다. 정말 중요한 것 하나는 "Last Seen(sec)", 지난번 출현(초) 열입니다. 리퍼런스되었음에도 콧배기도 안비치는 텍스처가 있으면, 바로 어디에 쓰였는지 조사해서 잡아낼 용의선상에 올리면 되겠습니다.

렌더 타겟을 제외한 텍스처는 모두 텍스처 풀을 사용합니다. 라이트맵도 물론입니다. (버텍스 라이팅 대신 라이트맵을 사용하면, 시스템 메모리가 출렁대지 않기에 메모리상 확실히 이득입니다.)

또한 게임에 최적인 텍스처 풀을 갖도록 하십시오. Stat Memory 로 텍스처 풀이 얼마나 사용되고 있는지 알 수 있습니다. 그에 따라 게임에 최적인 수치를 결정할 수 있을 것입니다.

DefaultEngine.ini 에서:

[TextureStreaming]
PoolSize=120

메모리 단편화


MEM STAT: 요약만

MEM DETAILED:

할당기에 대해 유용한 정보를 출력합니다. 아래 플랫폼 부분을 참고해 주십시오.

단편화 문제를 처리하려면 메모리 정도의 (플랫폼에 따른) 버퍼를 유지할 필요가 있습니다. 보고된 여유 메모리가 "0" 에 달하기 전, 게임에 아웃 오브 메모리 크래시가 날 것입니다.

리퍼런스된 애셋 찾기


게임의 레벨이 완성 시점에 가까워감에 따라, 전부 둘러보고 필요한 애셋만 로드되도록 확인하는 패스를 둘 가치는 충분히 있습니다. 언리얼 엔진 3 에서, 리퍼런스된 콘텐츠는 게임에 로드되며, 콘텐츠는 직접이든 간접이든 리퍼런스할 수 있는 방식이 많이 있기에, 부지불식간에 엄청난 애셋을 로드해 버리게 됩니다. 예를 들어 스켈레탈 메시를 자동으로 리퍼런스하는 폰은, 스켈레탈 메시에 의해 리퍼런스된 애님세트를 로드할 것이므로, 모든 애니메이션이 로드될 것입니다 (폰->스켈레탈 메시->애님세트->애니메이션). 이러한 연쇄 관계는 게임에 서로 리퍼런스되는 액터가 늘어갈수록 메모리 면에서 위험해질 수 있습니다.

로드하지 말아야 할 것들이 로드되는지, 그런 것들을 스트리밍 레벨에 분산시켜서 항상 로드되지 않게 할 수는 없는지 등에 대해 보통 레벨 디자이너와 상의하여 결정해야 할 것입니다. 이 레벨에는 존재하지 않는 적의 메시같은 것을 예로 들 수 있겠습니다.

MEMLEAKCHECK:

Memleakcheck 는 다량의 기본 게임 데이터를 [ProfileDirectory:Platform-specific]/MemLeak 안에 텍스트 파일로 출력합니다.

최소한 이를 통해 스켈레탈 메시, 애님세트, 사운드 등의 오브젝트 목록에 대한 스냅샷을 보여줄 것입니다. 기본적인 스냅샷이 생기면, 불필요한 애셋이 로드되지는 않았나 훑어보면 됩니다. 그렇다면 뭐가 그것을 리퍼런스했는지 추적해 내려가서 그 리퍼런스를 제거하는 것이 해결책입니다.

아래는 실전 사례입니다...

OBJ LIST CLASS=SKELETALMESH:

로드된 스켈레탈 메시를 크기별로 정렬하여 나열합니다. MEMLEAKCHECK 에도 이 정보가 포함되나 알파벳 순 정렬이기에 최적화할 콘텐츠를 찾기는 어렵습니다.

LISTSOUNDS:

로드된 사운드큐를 전부 나열합니다. MEMLEAKCHECK 에도 이 정보가 포함되나 알파벳 순 정렬이기에, 최적화할 콘텐츠를 찾기는 어렵습니다.

OBJ LIST CLASS=STATICMESH: Lists all StaticMeshes loaded, sorted by size.

LISTANIMSETS:

로드된 애님세트 전부를 크기별로 정렬하여 나열합니다. MEMLEAKCHECK 에도 이 정보가 포함됩니다.

OBJ REFS:

애셋이 로드되는 이유를 알아내기에 가장 좋은 방법은 OBJ REFS 명령입니다. 이 명령은 PC 에서만 실행됩니다. 재귀 함수때문에 딥 스택이 필요한데, UE3 콘솔 버전에서는 거의 크래시가 나기 때문입니다.

문제되는 애셋이 로드되는 지점까지 플레이한 다음, 이렇게 입력하십시오: OBJ REFS CLASS= NAME=

예를 들어:

OBJ REFS CLASS=SKELETALMESH NAME=BIG_OGRE_2

그러면 이 애셋을 끌어들이는 리퍼런스 체인이 무엇인지 표시됩니다:

Log: Shortest reachability from root to SkeletalMesh COG_Bolo_Grenade.Frag_Grenade:
Log:    SkeletalMesh COG_Bolo_Grenade.Frag_Grenade [target] (root) (standalone)
Log:    SkeletalMeshComponent GearGame.Default__GearProj_FragGrenade:COGGrenadeMesh0 (root) (ObjectProperty Engine.SkeletalMeshComponent:SkeletalMesh)
Log:    Class GearGame.GearProj_FragGrenade (root) (standalone)

위의 예제에서, Frag_Grenade 메시가 GearProj_FragGrenade 네이티브 클래스의 default properties 에서 리퍼런스되고 있습니다.

그 이상의 리퍼런스가 있을 수 있는데, 그런 경우에는 한 번에 하나씩 처리해 줘야 합니다.

애셋이 잘못 로드되는 이유에 대해 저희가 알아낸 바로는:

  • 애셋이 네이티브 클래스에 의해 리퍼런스되었습니다.
  • UnrealScript 코드가 default property 를 구하기 위해 (애셋을 리퍼런스하는) 클래스를 리퍼런스하였습니다:
            Asset = class'MyGameContent.Pawn_BigOgre'.default.Mesh.PhysicsAsset;
  • Touch 키즈멧 이벤트 (SeqEvt_Touch) 가 그 ClassProximityTypes 또는 IgnoredClassProximityTypes 배열의 클래스를 리퍼런스합니다.