Обнаружение узких мест в производительности с помощью Unity Frame Timing Manager

Создать выдающийся опыт, который будет плавно работать на различных устройствах и платформах, может быть непросто. Именно поэтому мы продолжаем совершенствовать наши инструменты, такие как улучшенный Frame Timing Manager, для оптимизации всех процессов. Читайте далее, чтобы узнать, как обновление Unity 2022.1 обеспечивает расширенную платформенную поддержку этой функции, позволяя Вам собирать больше данных, чем это было возможно ранее.
Frame Timing Manager - это функция, которая обеспечивает измерения времени на уровне кадра, например, общее время CPU и GPU в кадре. По сравнению с Unity Profiler общего назначения и Profiler API, Frame Timing Manager предназначен для выполнения очень специфической задачи, поэтому его производительность гораздо ниже. Количество собранной информации тщательно ограничено, так как в ней выделены только самые важные статистические данные каркаса.
Одна из основных причин использования Frame Timing Manager - более детальное исследование узких мест в производительности. Это позволит Вам определить, что сдерживает производительность Вашего приложения: Он привязан к основному потоку или потоку рендеринга на CPU, или он привязан к GPU? Основываясь на результатах анализа, Вы можете предпринять дальнейшие действия для улучшения производительности.
Функция динамического разрешения поддерживает устранение обнаруженных узких мест на стороне GPU. Затем Вы можете увеличить или уменьшить разрешение рендеринга, чтобы динамически контролировать объем работы на GPU.
Во время разработки Вы можете даже визуализировать тайминг в HUD приложения, что позволяет Вам иметь высокоуровневый мини-профайлер в реальном времени, встроенный прямо в Ваше приложение. Таким образом, он всегда будет под рукой.
Наконец, Вы можете использовать Frame Timing Manager для создания отчетов о производительности в режиме релиза. На основе собранной информации Вы можете отправить на свои серверы статистику о производительности Вашего приложения на разных платформах для принятия более эффективных решений.
API Frame Timing Manager предоставляет набор полезных измерений CPU и GPU за кадр в виде структуры FrameTiming. Вот их список:
- cpuFrameTime относится к общему времени кадра процессора. Он рассчитывается как время между началом кадра и следующим кадром в основном потоке.
- cpuMainThreadFrameTime - это время работы основного потока, или общее количество времени между началом кадра и завершением работы основного потока.
- cpuRenderThreadFrameTime относится к времени работы потока рендеринга, или общему количеству времени между первым рабочим запросом, отправленным потоку рендеринга, и моментом вызова функции Present().
- cpuMainThreadPresentWaitTime - это время, которое CPU тратит на ожидание завершения функции Present() в течение кадра.
- gpuFrameTime - это время работы GPU, или общее количество времени между подачей задания на GPU и сигналом, указывающим на то, что GPU закончил работу. См. соответствующие ограничения в разделе "Поддерживаемые платформы и ограничения" ниже.
Обратите внимание, что cpuMainThreadPresentWaitTime - это сумма показанных блоков "[wait]" и включает в себя ожидание Present() и целевого fps. Сложнее показать время работы GPU, поскольку оно начинается где-то в середине "Рендеринга сцены" и заканчивается в точке синхронизации следующего кадра с предыдущим.
Во-первых, стоит отметить, что менеджер времени кадров (Frame Timing Manager) всегда активен в сборках разработки. Если Вы планируете использовать его только в разработке, Вам не нужно выполнять никаких дополнительных действий - Вы можете просто воспользоваться API Frame Timing Manager C# или его счетчиками.
В релизных сборках Вам необходимо явно активировать функцию, прежде чем Вы сможете ею воспользоваться. Есть несколько способов сделать это. Один из простых подходов - установить флажок в настройках Project Player. В этом случае Вы можете читать данные, используя API C#. Однако, к сожалению, это наименее эффективный метод. Если Вы включите эту функцию в настройках, она останется активной независимо от того, нужна ли она Вам в конкретный момент времени.
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
}
}
}
csКроме того, Вы можете считывать значения Frame Timing Manager с помощью API Profiler Recorder. Преимущество API Profiler Recorder в том, что измерения 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
}
}
csДанные, предоставляемые диспетчером синхронизации кадров, могут быть использованы для обнаружения узких мест. В самом простом варианте Вы можете сравнить время CPU основного потока, CPU потока рендеринга, Present Wait и 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;
}
}
csFrame 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);
}
}
csМенеджер синхронизации кадров поддерживает все платформы, которые поддерживает Unity, за следующими исключениями:
- На платформах Linux, где используется API OpenGL, время работы GPU не предоставляется.
- На платформе WebGL время работы GPU не предоставляется.
- На iOS и macOS при использовании Metal API сообщалось, что время работы GPU может превышать общее время кадра при высокой нагрузке на GPU.
Важными особенностями реализации Менеджера синхронизации кадров являются:
Frame Timing Manager выдает результаты с фиксированной задержкой в четыре кадра. Это означает, что Вы получите результаты для кадра, который находится на четыре кадра позже (не для текущего кадра). Frame Timing Manager предоставляет измерения времени, синхронизированные для одного и того же кадра как для CPU, так и для GPU. Из-за ограничений платформы и аппаратного обеспечения результаты синхронизации GPU не сразу доступны на большинстве платформ.
Frame Timing Manager не гарантирует, что GPU будет доступен для всех кадров. GPU может не вернуть результаты вовремя или вообще не вернуть никаких результатов. В этих случаях значение GPU Frame Time будет равно нулю.
На платформах, не позволяющих использовать временные метки GPU, Unity вычисляет значение Frame Complete Time, а не измеряет его. Более конкретно, Unity вычисляет время завершения кадра как время первой отправки + время GPU. Если GPU не может предоставить время GPU, время завершения кадра автоматически устанавливается равным текущей временной метке.
На графических процессорах, использующих архитектуру отложенного рендеринга на основе плиток, например, на мобильных платформах, результаты будут менее точными, поскольку выполнение на GPU откладывается, и выполнение фаз рендеринга может происходить отдельно. Frame Timing Manager может измерять только общую продолжительность.
Для опытных пользователей Менеджер времени кадров предоставляет информацию о временных метках, которую можно использовать для визуализации временной шкалы кадров или вычисления разницы с другими маркерами.
Временные метки представлены следующим образом:
- frameStartTimestamp: Тактовое время процессора в момент первого запуска кадра
- firstSubmitTimestamp: Время на CPU, когда начальная работа передается на GPU во время выполнения кадра (зависит от платформы и API); разные платформы передают работу в разное время.
- cpuTimePresentCalled: Время процессорных часов в момент вызова Present() для данного кадра. Это время, когда Unity заканчивает отправку объектов на рендеринг и сообщает GPU, что кадр может быть представлен пользователю.
- cpuTimeFrameComplete: Тактовое время CPU в тот момент, когда GPU заканчивает рендеринг кадра. На большинстве платформ это значение рассчитывается и равно временной метке First Submit + время Frame GPU.
Мы надеемся, что эти улучшения помогут Вам измерить и понять уникальную историю производительности Вашего приложения. Эти преимущества теперь в Ваших руках с Unity 2022.1.
Если Вам интересно, что будет дальше с нашими инструментами профилирования, ознакомьтесь с нашей дорожной картой здесь. В противном случае, пожалуйста, не стесняйтесь обращаться к команде на нашем форуме. Мы будем рады услышать Ваши мысли и узнать, как мы можем еще больше улучшить возможности и инструменты Unity в плане производительности.
