다양한 기기와 플랫폼에서 원활하게 실행되는 뛰어난 경험을 제작하는 작업에는 여러 가지 어려움이 수반될 수 있습니다. 이에 따라 유니티에서는 지속적으로 Frame Timing Manager와 같은 툴을 개선하여 전반적으로 최적화를 진행하고 있습니다. Unity 2022.1 업데이트를 통해 어떻게 이 기능에 대한 플랫폼 지원이 개선되어 이전보다 더 많은 데이터를 수집할 수 있는지 살펴보세요.
Frame Timing Manager가 제공하는 이점
Frame Timing Manager는 총 프레임 CPU 및 GPU 시간과 같이 프레임 수준의 시간 측정치를 제공하는 기능입니다. 일반적인 용도의 Unity Profiler와 Profiler API에 비해 Frame Timing Manager는 매우 구체적인 작업용으로 디자인되었기 때문에 성능 오버헤드가 훨씬 더 낮습니다. 가장 중요한 프레임 스탯에 집중하기 때문에 수집되는 정보의 양이 면밀히 제한됩니다.
Frame Timing Manager를 활용하는 주된 이유 하나는 성능 병목 현상을 더 자세히 조사하기 위해서입니다. 이를 통해 애플리케이션 성능을 제한하는 요소를 파악할 수 있습니다. 예를 들어 CPU의 메인 스레드 또는 렌더 스레드에 따른 제한인지, GPU로 인한 제한인지 확인하고 이러한 분석을 토대로 추가 조치를 취해 성능을 개선할 수 있습니다.
다이내믹 해상도 기능을 통해 GPU 측에서 감지된 병목 현상을 해결한 다음, 렌더링 해상도를 높이거나 낮춰 GPU에서 작업량을 동적으로 제어할 수 있습니다.
개발 도중에 애플리케이션 HUD에서 타이밍도 시각화할 수 있으므로 대략적인 수준의 실시간 미니 프로파일러를 애플리케이션에 바로 구축할 수 있습니다. 이 방식을 활용하면 언제든지 사용이 가능합니다.
마지막으로 릴리스 모드 성능 보고를 위해 Frame Timing Manager를 사용할 수 있습니다. 수집된 정보를 토대로, 여러 플랫폼에서의 애플리케이션 성능에 관한 통계를 서버로 전송함으로써 전반적으로 더 나은 의사 결정을 내릴 수 있습니다.
Frame Timing Manager API에서 제공하는 측정치
Frame Timing Manager API는 유용한 프레임당 CPU 및 GPU 측정치를 FrameTiming 구조체로 제공합니다. 이러한 구조체의 목록은 아래와 같습니다.
cpuMainThreadPresentWaitTime은 표시된 '[wait]' 블록의 합이며, Present() 및 타겟 FPS 대기를 포함합니다. GPU 작업 시간의 경우 'Scene rendering' 과정의 중간 어디쯤에서 시작하여 다음 프레임이 이전 프레임과 동기화되는 지점에서 끝나기 때문에 표시하기가 어렵습니다.
시작하는 방법
첫 번째로, 개발 빌드에서 Frame Timing Manager는 항상 활성화 상태라는 점을 알아두는 것이 좋습니다. 개발 과정에서만 사용하려는 경우에는 추가 단계를 완료하지 않아도 되고, Frame Timing Manager C# API 또는 카운터를 사용하기만 하면 됩니다.
반면 릴리스 빌드에서는 기능을 명시적으로 활성화한 후에 사용해야 하며, 활성화하는 방법에는 여러 가지가 있습니다. 가장 간단한 방식으로 Project Player 설정에서 체크박스를 선택하면 되는데 이 경우에는 C# API를 사용하여 데이터를 읽을 수 있습니다. 하지만 이 방법은 가장 비효율적인 방법입니다. 설정에서 기능을 활성화하면 특정 시점에 필요하든 필요하지 않든 기능이 활성화 상태로 유지됩니다.
using Unity.Profiling;
using UnityEngine;
public class ExampleScript : MonoBehaviour
{
FrameTiming[] m_FrameTimings = new FrameTiming[10];
void Update()
{
// Instruct FrameTimingManager to collect and cache information
FrameTimingManager.CaptureFrameTimings();
// Read cached information about N last frames (10 in this example)
// The returned value tells how many samples is actually returned
var ret = FrameTimingManager.GetLatestTimings((uint)m_FrameTimings.Length, m_FrameTimings);
if (ret > 0)
{
// Your code logic here
}
}
}
또 다른 방식으로 Profiler Recorder API를 사용하여 Frame Timing Manager 값을 읽을 수 있습니다. Profiler Recorder API의 이점은 레코더를 카운터에 연결했을 때만 Frame Timing Manager 측정치가 얻어지기 때문에 기능과 오버헤드를 동적으로 제어할 수 있다는 점입니다.
using Unity.Profiling;
using UnityEngine;
public class ExampleScript : MonoBehaviour
{
ProfilerRecorder mainThreadTimeRecorder;
void OnEnable()
{
// Create ProfilerRecorder and attach it to a counter
mainThreadTimeRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "CPU Main Thread Frame Time");
}
void OnDisable()
{
// Recorders must be explicitly disposed after use
mainThreadTimeRecorder.Dispose();
}
void Update()
{
var frameTime = mainThreadTimeRecorder.LastValue;
// Your code logic here
}
}
Frame Timing Manager에서 제공된 데이터를 사용해 병목 현상을 감지할 수 있습니다. 가장 단순한 배리언트에서는 메인 스레드 CPU, 렌더 스레드 CPU, Present 대기, GPU 시간을 비교하여 프레임 속도 제한을 유발하는 데 가장 높은 비중을 차지하면서도 유력한 원인을 파악할 수 있습니다. 예를 들면 다음과 같습니다.
using Unity.Profiling;
using UnityEngine;
public class ExampleScript : MonoBehaviour
{
internal enum PerformanceBottleneck
{
Indeterminate, // Cannot be determined
PresentLimited, // Limited by presentation (vsync or framerate cap)
CPU, // Limited by CPU (main and/or render thread)
GPU, // Limited by GPU
Balanced, // Limited by both CPU and GPU, i.e. well balanced
}
FrameTiming[] m_FrameTimings = new FrameTiming[1];
void Update()
{
FrameTimingManager.CaptureFrameTimings();
var ret = FrameTimingManager.GetLatestTimings((uint)m_FrameTimings.Length, m_FrameTimings);
if (ret > 0)
{
var bottleneck = DetermineBottleneck(m_FrameTimings[0]);
// Your code logic here
}
}
static PerformanceBottleneck DetermineBottleneck(FrameTimeSample s)
{
const float kNearFullFrameTimeThresholdPercent = 0.2f;
const float kNonZeroPresentWaitTimeMs = 0.5f;
// If we're on platform which doesn't support GPU time
if (s.GPUFrameTime == 0)
return PerformanceBottleneck.Indeterminate;
float fullFrameTimeWithMargin = (1f - kNearFullFrameTimeThresholdPercent) * s.FullFrameTime;
// GPU time is close to frame time, CPU times are not
if (s.GPUFrameTime > fullFrameTimeWithMargin &&
s.MainThreadCPUFrameTime < fullFrameTimeWithMargin &&
s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin)
return PerformanceBottleneck.GPU;
// One of the CPU times is close to frame time, GPU is not
if (s.GPUFrameTime < fullFrameTimeWithMargin &&
(s.MainThreadCPUFrameTime > fullFrameTimeWithMargin ||
s.RenderThreadCPUFrameTime > fullFrameTimeWithMargin))
return PerformanceBottleneck.CPU;
// Main thread waited due to Vsync or target frame rate
if (s.MainThreadCPUPresentWaitTime > kNonZeroPresentWaitTimeMs)
{
// None of the times are close to frame time
if (s.GPUFrameTime < fullFrameTimeWithMargin &&
s.MainThreadCPUFrameTime < fullFrameTimeWithMargin &&
s.RenderThreadCPUFrameTime < fullFrameTimeWithMargin)
return PerformanceBottleneck.PresentLimited;
}
return PerformanceBottleneck.Balanced;
}
}
Frame Timing Manager를 단순한 화면상의 프로파일러로 사용할 수 있고, 이렇게 하면 애플리케이션 상태를 평가할 때 유용합니다. 가장 기본적인 형태는 다음과 같습니다.
using System;
using UnityEngine;
using Unity.Profiling;
public class FrameTimingsHUDDisplay : MonoBehaviour
{
GUIStyle m_Style;
readonly FrameTiming[] m_FrameTimings = new FrameTiming[1];
void Awake()
{
m_Style = new GUIStyle();
m_Style.fontSize = 15;
m_Style.normal.textColor = Color.white;
}
void OnGUI()
{
CaptureTimings();
var reportMsg =
$"\nCPU: {m_FrameTimings[0].cpuFrameTime :00.00}" +
$"\nMain Thread: {m_FrameTimings[0].cpuMainThreadFrameTime:00.00}" +
$"\nRender Thread: {m_FrameTimings[0].cpuRenderThreadFrameTime:00.00}" +
$"\nGPU: {m_FrameTimings[0].gpuFrameTime:00.00}";
var oldColor = GUI.color;
GUI.color = new Color(1, 1, 1, 1);
float w = 300, h = 210;
GUILayout.BeginArea(new Rect(32, 50, w, h), "Frame Stats", GUI.skin.window);
GUILayout.Label(reportMsg, m_Style);
GUILayout.EndArea();
GUI.color = oldColor;
}
private void CaptureTimings()
{
FrameTimingManager.CaptureFrameTimings();
FrameTimingManager.GetLatestTimings(m_FrameTimings.Length, m_FrameTimings);
}
}
지원되는 플랫폼 및 제한 사항
Frame Timing Manager는 Unity에서 지원되는 모든 플랫폼을 지원하나, 다음과 같은 예외 사항이 있습니다.
Frame Timing Manager의 구현 관련 중요 세부 사항은 다음과 같습니다.
심화 주제
고급 수준의 사용자라면 Frame Timing Manager를 통해 프레임 타임라인 시각화 또는 다른 마커를 활용한 델타 계산에 사용할 수 있는 타임스탬프 정보를 제공받을 수 있습니다.
다음과 같은 타임스탬프가 제공됩니다.
의견을 들려 주세요
이러한 개선 사항을 통해 애플리케이션의 고유한 성능을 측정하고 관련 내용을 파악하실 수 있기를 바랍니다. 이제 Unity 2022.1을 통해 이러한 장점을 활용하실 수 있습니다.
Unity 프로파일링 툴의 다음 목표가 궁금하시다면 여기에서 유니티의 로드맵을 확인하세요. 또는 유니티 포럼에서 유니티 팀에 문의해 주세요. 유니티는 여러분의 의견에 귀를 기울이고 Unity의 성능 기능과 툴을 더욱 개선할 방안을 모색합니다.