통계 시스템(Stats System) 을 통해 퍼포먼스 데이터를 수집 및 표시하여 게임 최적화에 활용할 수 있습니다.
통계 명령 도움말이 필요하면 콘솔에 stat
을 입력하거나 PrintStatsHelpToOutputDevice();
메서드를 참조합니다.
타입
통계 시스템은 다음 타입을 지원합니다.
통계 타입 | 설명 |
---|---|
주기 카운터(Cycle Counter) | 오브젝트의 수명 동안 주기 횟수를 세는 데 사용되는 일반적 주기 카운터입니다. |
Float/Dword 카운터 | 매 프레임마다 지워지는 카운터입니다. |
Float/Dword 축적기 | 매 프레임마다 지워지지 않고 지속적으로 유지되는 통계 카운터입니다. 리셋 가능합니다. |
메모리 | 메모리 트래킹에 최적화된 특수 타입 카운터입니다. |
통계 그룹화
각 통계는 그룹화되어야 하며, 이는 보통 특정 통계 그룹의 표시에 대응합니다. 예를 들어 stat statsystem 은 통계 관련 데이터를 표시합니다.
통계 그룹을 정의하려면 다음 메서드 중 하나를 사용합니다.
메서드 | 설명 |
---|---|
DECLARE_STATS_GROUP(GroupDesc, GroupId, GroupCat) |
디폴트로 활성화된 통계 그룹을 선언합니다. |
DECLARE_STATS_GROUP_VERBOSE(GroupDesc, GroupId, GroupCat) |
디폴트로 비활성화된 통계 그룹을 선언합니다. |
DECLARE_STATS_GROUP_MAYBE_COMPILED_OUT(GroupDesc, GroupId, GroupCat) |
디폴트로 비활성화되고 컴파일러에 의해 제거될 수 있는 통계 그룹을 선언합니다. |
축약어 뜻:
GroupDesc
는 그룹에 관한 텍스트 설명입니다.GroupId
는 그룹의UNIQUE
ID입니다.GroupCat
은 향후 사용을 위해 예약됩니다.CompileIn
은 true로 설정될 경우 컴파일러에 의해 제거될 수 있습니다.
사용 범위에 따라 그룹화는 소스 또는 헤더 파일에서 수행될 수 있습니다.
사용 예시
DECLARE_STATS_GROUP(TEXT("Threading"), STATGROUP_Threading, STATCAT_Advanced);
DECLARE_STATS_GROUP_VERBOSE(TEXT("Linker Load"), STATGROUP_LinkerLoad, STATCAT_Advanced);
통계 선언 및 정의하기
이제 통계를 선언 및 정의할 수 있지만, 그 전에 통계는 다음에서 사용할 수 있다는 점에 유의하세요.
- 단 하나의 cpp 파일
- 함수 범위
- 모듈 범위
- 전체 프로젝트
단일 파일의 경우
단일 파일 범위의 경우 통계 타입에 따라 다음 메서드 중 하나를 사용해야 합니다.
메서드 | 설명 |
---|---|
DECLARE_CYCLE_STAT(CounterName, StatId, GroupId) |
주기 카운터 통계를 선언합니다. |
DECLARE_SCOPE_CYCLE_COUNTER(CounterName, StatId, GroupId) |
주기 카운터 통계를 선언하는 동시에 사용합니다. 또한 하나의 함수 범위로 제한됩니다. |
QUICK_SCOPE_CYCLE_COUNTER(StatId) |
'Quick'이라고 명명된 통계 그룹에 속할 주기 카운터 통계를 선언합니다. |
RETURN_QUICK_DECLARE_CYCLE_STAT(StatId, GroupId) |
주기 카운터를 반환합니다. 가끔 일부 특수 클래스에서 사용됩니다. |
DECLARE_FLOAT_COUNTER_STAT(CounterName, StatId, GroupId) |
float 카운터를 반환하며 double 타입(8바이트)을 기반으로 합니다. |
DECLARE_DWORD_COUNTER_STAT(CounterName, StatId, GroupId) |
dword 카운터를 반환하며 qword 타입(8바이트)을 기반으로 합니다. |
DECLARE_FLOAT_ACCUMULATOR_STAT(CounterName, StatId, GroupId) |
float 축적기를 선언합니다. |
DECLARE_DWORD_ACCUMULATOR_STAT(CounterName, StatId, GroupId) |
dword 축적기를 선언합니다. |
DECLARE_MEMORY_STAT(CounterName, StatId, GroupId) |
dword 축적기와 동일한 메모리 카운터를 선언합니다. 하지만 특정 메모리 단위로 표시됩니다. |
DECLARE_MEMORY_STAT_POOL(CounterName, StatId, GroupId, Pool) |
풀이 있는 메모리 카운터를 선언합니다. |
다수 파일의 경우
통계를 전체 프로젝트(또는 폭넓은 파일 범위)에서 액세스 가능하게 하려면 extern 버전을 사용해야 합니다. 이 메서드는 이전에 언급한 것과 같지만, 이름 끝에 _EXTERN
이 붙습니다.
DECLARE_CYCLE_STAT_EXTERN(CounterName, StatId, GroupId, API)
DECLARE_FLOAT_COUNTER_STAT_EXTERN(CounterName, StatId, GroupId, API)
DECLARE_DWORD_COUNTER_STAT_EXTERN(CounterName, StatId, GroupId, API)
DECLARE_FLOAT_ACCUMULATOR_STAT_EXTERN(CounterName, StatId, GroupId, API)
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(CounterName, StatId, GroupId, API)
DECLARE_MEMORY_STAT_EXTERN(CounterName, StatId, GroupId, API)
DECLARE_MEMORY_STAT_POOL_EXTERN(CounterName, StatId, GroupId, Pool, API)
그런 다음 소스 파일에서 다음 통계를 정의해야 합니다. 이는 _EXTERN
을 붙여 선언한 통계를 정의합니다.
축약어 뜻:
CounterName
은 통계에 관한 텍스트 설명입니다.StatId
는 통계의UNIQUE
ID입니다.GroupId
는 통계가 속할 그룹의 ID이며,DECLARE_STATS_GROUP*
에서 온GroupId
입니다.Pool
은 플랫폼 전용 메모리 풀입니다.API
는 모듈의*_API
이며 통계가 해당 모듈에서만 사용되는 경우 비어 있을 수 있습니다.
예제
풀이 있는 커스텀 메모리 통계
우선 enum EMemoryCounterRegion
에 새 풀을 추가해야 합니다. 이는 글로벌일 수도 있고 플랫폼 전용일 수도 있습니다.
enum EMemoryCounterRegion
{
MCR_Invalid, // 메모리 아님
MCR_Physical, // 메인 시스템 메모리
MCR_GPU, // GPU의 직접 메모리(그래픽 카드)
MCR_GPUSystem, // GPU로 직접 액세스 가능한 시스템 메모리
MCR_TexturePool, // 사전에 크기가 지정된 텍스처 풀
MCR_MAX
};
다음은 풀을 모든 곳에서 사용하도록 허용하는 예시입니다(CORE_API
참조).
풀 이름은 MCR_
로 시작되어야 합니다.
헤더 파일 샘플
DECLARE_MEMORY_STAT_POOL_EXTERN(TEXT("Physical Memory Pool [Physical]"), MCR_Physical, STATGROUP_Memory, FPlatformMemory::MCR_Physical, CORE_API);
DECLARE_MEMORY_STAT_POOL_EXTERN(TEXT("GPU Memory Pool [GPU]"), MCR_GPU,STATGROUP_Memory, FPlatformMemory::MCR_GPU, CORE_API);
DECLARE_MEMORY_STAT_POOL_EXTERN(TEXT("Texture Memory Pool [Texture]"), MCR_TexturePool, STATGROUP_Memory, FPlatformMemory::MCR_TexturePool,CORE_API);
소스 파일 샘플
DEFINE_STAT(MCR_Physical);
DEFINE_STAT(MCR_GPU);
DEFINE_STAT(MCR_TexturePool);
// 이것은 풀이므로 초기화되어야 합니다. 보통 F*PlatformMemory::Init()에서 수행됩니다.
SET_MEMORY_STAT(MCR_Physical, PhysicalPoolLimit);
SET_MEMORY_STAT(MCR_GPU, GPUPoolLimit);
SET_MEMORY_STAT(MCR_TexturePool, TexturePoolLimit);
// 풀이 생겼으니 풀을 위한 메모리 통계를 구성해야 합니다.
// 다음은 어디서든 액세스 가능합니다.
DECLARE_MEMORY_STAT_POOL_EXTERN(TEXT("Index buffer memory"), STAT_IndexBufferMemory, STATGROUP_RHI, FPlatformMemory::MCR_GPU, RHI_API);
DECLARE_MEMORY_STAT_POOL_EXTERN(TEXT("Vertex buffer memory"), STAT_VertexBufferMemory, STATGROUP_RHI, FPlatformMemory::MCR_GPU, RHI_API);
DECLARE_MEMORY_STAT_POOL_EXTERN(TEXT("Structured buffer memory"), STAT_StructuredBufferMemory,STATGROUP_RHI, FPlatformMemory::MCR_GPU, RHI_API);
DECLARE_MEMORY_STAT_POOL_EXTERN(TEXT("Pixel buffer memory"), STAT_PixelBufferMemory, STATGROUP_RHI, FPlatformMemory::MCR_GPU, RHI_API);
// 다음은 정의된 모듈에서만 액세스 가능합니다.
DECLARE_MEMORY_STAT_POOL_EXTERN(TEXT("Pool Memory Size"), STAT_TexturePoolSize, STATGROUP_Streaming, FPlatformMemory::MCR_TexturePool, );
DECLARE_MEMORY_STAT_POOL_EXTERN(TEXT("Pool Memory Used"), STAT_TexturePoolAllocatedSize, STATGROUP_Streaming, FPlatformMemory::MCR_TexturePool, );
// 마지막으로 메모리 통계를 업데이트해야 합니다.
// 메모리 통계를 정해진 값만큼 늘립니다.
INC_MEMORY_STAT_BY(STAT_PixelBufferMemory,NumBytes);
// 메모리 통계를 정해진 값만큼 줄입니다.
DEC_MEMORY_STAT_BY(STAT_PixelBufferMemory,NumBytes);
// 메모리 통계를 정해진 값으로 설정합니다.
SET_MEMORY_STAT(STAT_PixelBufferMemory,NumBytes);
풀이 없는 정규 메모리 통계
DECLARE_MEMORY_STAT(TEXT("Total Physical"), STAT_TotalPhysical, STATGROUP_MemoryPlatform);
DECLARE_MEMORY_STAT(TEXT("Total Virtual"), STAT_TotalVirtual, STATGROUP_MemoryPlatform);
DECLARE_MEMORY_STAT(TEXT("Page Size"), STAT_PageSize, STATGROUP_MemoryPlatform);
DECLARE_MEMORY_STAT(TEXT("Total Physical GB"), STAT_TotalPhysicalGB, STATGROUP_MemoryPlatform);
위 예시 말고 다른 방법으로는 헤더 파일에서 DECLARE_MEMORY_STAT_EXTERN
을 사용한 다음 소스 파일에서 DEFINE_STAT
을 사용할 수도 있습니다.
메모리 통계 업데이트는 풀이 있는 버전과 똑같이 이뤄집니다.
주기 카운터를 사용한 퍼포먼스 데이터
우선 주기 카운터를 추가해야 합니다.
DECLARE_CYCLE_STAT(TEXT("Broadcast"), STAT_StatsBroadcast, STATGROUP_StatSystem);
DECLARE_CYCLE_STAT(TEXT("Condense"), STAT_StatsCondense, STATGROUP_StatSystem);
위 예시 말고 다른 방법으로는 헤더 파일에서 DECLARE_MEMORY_STAT_EXTERN
을 사용한 다음 소스 파일에서 DEFINE_STAT
을 사용할 수 있습니다.
다음으로 퍼포먼스 데이터를 가져옵니다.
Stats::Broadcast()
{
SCOPE_CYCLE_COUNTER(STAT_StatsBroadcast);
// ...
// 코드 부분
// ...
}
함수가 호출될 때마다 통계를 매번 가져오지 않으려면 조건부 주기 카운터를 사용합니다. 흔하지는 않지만 경우에 따라 유용합니다.
Stats::Broadcast(bool bSomeCondition)
{
CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_StatsBroadcast,bSomeCondition);
// ...
// 코드 부분
// ...
}
한 함수에서 퍼포먼스 데이터를 가져오려면 다음 구성을 사용할 수 있습니다.
Stats::Broadcast(bool bSomeCondition)
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Broadcast"), STAT_StatsBroadcast, STATGROUP_StatSystem);
// ...
// 코드 부분
// ...
}
이렇게 처리해도 됩니다.
Stats::Broadcast(bool bSomeCondition)
{
QUICK_SCOPE_CYCLE_COUNTER(TEXT("Stats::Broadcast"));
// ...
// 코드 부분
// ...
}
이는 대체로 임시 통계에 사용됩니다.
앞서 언급된 모든 주기 카운터는 계층구조를 생성하여 더욱 상세한 퍼포먼스 데이터 관련 정보를 얻고자 할 때 사용됩니다. 그러나 플랫 주기 카운터를 설정하는 옵션도 있습니다.
Stats::Broadcast(bool bSomeCondition)
{
const uint32 BroadcastBeginTime = FPlatformTime::Cycles();
// ...
// 코드 부분
// ...
const uint32 BroadcastEndTime = FPlatformTime::Cycles();
SET_CYCLE_COUNTER(STAT_StatsBroadcast, BroadcastEndTime-BroadcastBeginTime);
}
GetStatId를 사용한 퍼포먼스 데이터
언리얼 엔진에 구현된 몇 가지 태스크는 퍼포먼스 데이터를 가져오기 위해 다른 접근법을 사용합니다. 이런 태스크는 GetStatId()
메서드를 구현하며, GetStatId()
가 없는 경우 코드가 컴파일되지 않습니다.
예시는 다음과 같습니다.
class FParallelAnimationCompletionTask
{
// ...
// 코드 부분
// ...
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FParallelAnimationCompletionTask, STATGROUP_TaskGraphTasks);
}
// ...
// 코드 부분
// ...
};
퍼포먼스 데이터 로깅
퍼포먼스 데이터를 로깅만 하려는 경우를 위해 다음 함수 기능이 있습니다.
메서드
다음 메서드는 시간(단위: 초)을 캡처하고 전달되는 변수에 델타 시간을 추가합니다.
`SCOPE_SECONDS_COUNTER(double& Seconds)`
코드 샘플
Stats::Broadcast()
{
double ThisTime = 0;
{
SCOPE_SECONDS_COUNTER(ThisTime);
// ...
// 코드 부분
// ...
}
UE_LOG(LogTemp, Log, TEXT("Stats::Broadcast %.2f"), ThisTime);
}
유틸리티 클래스 및 메서드
클래스 | 설명 |
---|---|
FScopeLogTime |
시간(단위: 초)을 로깅하는 유틸리티 클래스입니다. 전달된 변수에 누적 통계를 추가하며 퍼포먼스 데이터를 소멸자의 로그에 출력합니다. |
메서드 | 설명 |
SCOPE_LOG_TIME(Name, CumulativePtr) |
제공된 이름을 사용하여 퍼포먼스 데이터를 출력하고 누적 통계를 수집합니다. |
SCOPE_LOG_TIME_IN_SECONDS(Name, CumulativePtr) |
SCOPE_LOG_TIME과 동일한 함수 기능이지만 초 단위로 출력합니다. |
SCOPE_LOG_TIME_FUNC() |
함수 이름을 사용하여 퍼포먼스 데이터를 출력하며 중첩이 불가능합니다. |
SCOPE_LOG_TIME_FUNC_WITH_GLOBAL(CumulativePtr) |
SCOPE_LOG_TIME_FUNC와 동일한 함수 기능이지만 누적 통계를 수집합니다. |
코드 샘플
double GMyBroadcastTime = 0.0;
Stats::Broadcast()
{
SCOPE_LOG_TIME("Stats::Broadcast", &GMyBroadcastTime);
SCOPE_LOG_TIME_IN_SECONDS("Stats::Broadcast (sec)", &GMyBroadcastTime);
// ...
// 코드 부분
// ...
}
Stats::Condense()
{
SCOPE_LOG_TIME_FUNC(); // The name should be "Stats::Condense()", may differ across compilers
SCOPE_LOG_TIME_FUNC_WITH_GLOBAL(&GMyBroadcastTime);
// ...
// 코드 부분
// ...
}
FLOAT 또는 DWORD 카운터를 사용한 일반 데이터
일반적으로 가장 먼저 다음과 같이 카운터를 추가해야 합니다.
DECLARE_FLOAT_COUNTER_STAT_EXTERN(STAT_FloatCounter,StatId,STATGROUP_TestGroup, CORE_API);
DECLARE_DWORD_COUNTER_STAT_EXTERN(STAT_DwordCounter,StatId,STATGROUP_TestGroup, CORE_API);
DECLARE_FLOAT_ACCUMULATOR_STAT_EXTERN(STAT_FloatAccumulator,StatId,STATGROUP_TestGroup, CORE_API);
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(STAT_DwordAccumulator,StatId,STATGROUP_TestGroup, CORE_API);
카운터를 업데이트 및 관리하려면 다음 메서드를 사용합니다.
카운터 업데이트 메서드
메서드 | 설명 |
---|---|
INC_DWORD_STAT(StatId) |
dword 통계를 1 늘립니다. |
DEC_DWORD_STAT(StatId) |
dword 통계를 1 줄입니다. |
INC_DWORD_STAT_BY(StatId, Amount) |
dword 통계를 지정된 값만큼 늘립니다. |
DEC_DWORD_STAT_BY(StatId, Amount) |
dword 통계를 지정된 값만큼 줄입니다. |
SET_DWORD_STAT(StatId, Value) |
dword 통계를 지정된 값으로 설정합니다. |
INC_FLOAT_STAT_BY(StatId, Amount) |
float 통계를 지정된 값만큼 늘립니다. |
DEC_FLOAT_STAT_BY(StatId, Amount) |
float 통계를 지정된 값만큼 줄입니다. |
SET_FLOAT_STAT(StatId, Value) |
float 통계를 지정된 값으로 설정합니다. |
카운터 관리를 위한 헬퍼 메서드
메서드 | 설명 |
---|---|
GET_STATID(StatId) |
통계의 TStatId 인스턴스를 반환합니다. |
GET_STATDESCRIPTION(StatId) |
통계 설명을 반환합니다. |