게임엔진/크로스플랫폼 : HazelEngine
230510 자체 엔진 개발 : Visual Profiling
mrawesome
2023. 5. 29. 16:24
https://github.com/ohbumjun/GameEngineTutorial/commit/b3c16ce7fa083209accd7c4a5815859c38b931bd
이와 같이 각각의 함수가 얼만큼의 시간을 소요하는지 Visual 적으로 Profiling 하는 System 을 마련해보고자 한다.
class InstrumentationTimer
{
public:
InstrumentationTimer(const char* name)
: m_Name(name), m_Stopped(false)
{
// 시간 간격을 주기 위해 2번 호출
m_StartTimepoint = std::chrono::high_resolution_clock::now();
m_StartTimepoint = std::chrono::high_resolution_clock::now();
}
~InstrumentationTimer()
{
if (!m_Stopped)
Stop();
}
void Stop()
{
auto endTimepoint = std::chrono::high_resolution_clock::now();
long long start = std::chrono::time_point_cast<std::chrono::microseconds>(m_StartTimepoint).time_since_epoch().count();
long long end = std::chrono::time_point_cast<std::chrono::microseconds
>(endTimepoint).time_since_epoch().count();
uint32_t threadID = std::hash<std::thread::id>{}(std::this_thread::get_id());
Instrumentor::Get().WriteProfile({ m_Name, start, end, threadID });
m_Stopped = true;
}
private:
const char* m_Name;
std::chrono::time_point<std::chrono::high_resolution_clock> m_StartTimepoint;
bool m_Stopped;
};
위의 Class 는 실제 소요된 시간을 계산할 때 사용하는 Class 이다.
기본적으로 Chrono Library 를 사용하는 것을 알 수 있다.
struct ProfileResult
{
std::string Name;
long long Start, End;
uint32_t ThreadID;
};
struct InstrumentationSession
{
std::string Name;
};
class Instrumentor
{
private:
InstrumentationSession* m_CurrentSession;
std::ofstream m_OutputStream;
int m_ProfileCount;
public:
Instrumentor()
: m_CurrentSession(nullptr), m_ProfileCount(0)
{
}
void BeginSession(const std::string& name, const std::string& filepath = "results.json")
{
m_OutputStream.open(filepath);
WriteHeader();
m_CurrentSession = new InstrumentationSession{ name };
}
void EndSession()
{
WriteFooter();
m_OutputStream.close();
delete m_CurrentSession;
m_CurrentSession = nullptr;
m_ProfileCount = 0;
}
void WriteProfile(const ProfileResult& result)
{
if (m_ProfileCount++ > 0)
m_OutputStream << ",";
std::string name = result.Name;
std::replace(name.begin(), name.end(), '"', '\'');
/*
'cat' – the category for this event.Useful when doing larger grouping(eg "UnitUpdates")
'name' – the name of this event(eg 'PathingUpdate')
'pid' – the processor ID that spawned this event
'tid' – the thread ID that spawned this event
'ts' – the processor time stamp at the time this event was created
'ph' – the phase or type of this event
'args' – any programmatic metadata that's attached to this event
*/
m_OutputStream << "{";
m_OutputStream << "\"cat\":\"function\",";
m_OutputStream << "\"dur\":" << (result.End - result.Start) << ',';
m_OutputStream << "\"name\":\"" << name << "\",";
m_OutputStream << "\"ph\":\"X\",";
m_OutputStream << "\"pid\":0,";
m_OutputStream << "\"tid\":" << result.ThreadID << ",";
m_OutputStream << "\"ts\":" << result.Start;
m_OutputStream << "}";
m_OutputStream.flush();
}
void WriteHeader()
{
m_OutputStream << "{\"otherData\": {},\"traceEvents\":[";
m_OutputStream.flush();
}
void WriteFooter()
{
m_OutputStream << "]}";
m_OutputStream.flush();
}
static Instrumentor& Get()
{
static Instrumentor instance;
return instance;
}
};
우리가 사용하는 Visual Profiling 은 Chrome/testing 을 사용하고
해당 사이트는 특정 format 의 Json 파일을 요구한다.
우리가 얻어낸 정보를 json 파일로 변환해주는 클래스이다.
Timer Class, Instrumentor 클래스 모두 프로그램 전체에서 하나의 Instance만 있으면
되기 때문에 static 변수를 활용할 것이다.
#if HZ_PROFILE
#define HZ_PROFILE_BEGIN_SESSION(name, filePath) ::Hazel::Instrumentor::Get().BeginSession(name, filePath);
#define HZ_PROFILE_END_SESSION() ::Hazel::Instrumentor::Get().EndSession();
#define HZ_PROFILE_SCOPE(name) ::Hazel::InstrumentationTimer time##__LINE__(name);
// __FUNCTION__ : name of function
// __FUNCSIG__ : name 뿐만 아니라 arg 를 통한 구분
#define HZ_PROFILE_FUNCTION() HZ_PROFILE_SCOPE(__FUNCSIG__);
#else
위와 같은 매크로를 정의한다.
1) Json 파일로 만들어내고자 하는 영역의 시간과 끝을 begin session, end session 매크로로 세팅한다.
2) 실제 시간을 calculate 하고자 하는 함수에 HZ_PROFILE_FUNCTION 을 세팅하게 되면, 해당 함수 범위 내에서
Timer Class 객체를 만들어서 소요된 시간을 json 에 기록한다.
완료시 위와 같은 이미지 형태를 얻을 수 있다.