Detecção de gargalos de desempenho com o Unity Frame Timing Manager

ANTON KRUGLYAKOV / UNITYSenior Software Engineer
Jun 16, 2022|14 Min
Detecção de gargalos de desempenho com o Unity Frame Timing Manager
Esta página da Web foi automaticamente traduzida para sua conveniência. Não podemos garantir a precisão ou a confiabilidade do conteúdo traduzido. Se tiver dúvidas sobre a precisão do conteúdo traduzido, consulte a versão oficial em inglês da página da Web.

Criar uma experiência de destaque que funcione sem problemas em uma variedade de dispositivos e plataformas pode ser um desafio. É por isso que continuamos a refinar nossas ferramentas, como o Frame Timing Manager aprimorado, para otimização em todas as áreas. Continue lendo para descobrir como a atualização do Unity 2022.1 oferece suporte aprimorado à plataforma para esse recurso, permitindo que o senhor colete mais dados do que era possível anteriormente.

O que o Frame Timing Manager pode fazer pelo senhor?

O Frame Timing Manager é um recurso que fornece medições de tempo no nível do quadro, como o tempo total de CPU e GPU do quadro. Em comparação com o Unity Profiler e a API do Profiler de uso geral, o Frame Timing Manager foi projetado para uma tarefa muito específica e, portanto, vem com uma sobrecarga de desempenho muito menor. A quantidade de informações coletadas é cuidadosamente limitada, pois destaca apenas as estatísticas de quadro mais importantes.

Um dos principais motivos para aproveitar o Frame Timing Manager é investigar os gargalos de desempenho com mais detalhes. Isso permite que o senhor determine o que limita o desempenho do seu aplicativo: Ele é vinculado ao thread principal ou ao thread de renderização na CPU, ou é vinculado à GPU? Com base em sua análise, o senhor pode tomar outras medidas para melhorar o desempenho.

O recurso de resolução dinâmica permite corrigir os gargalos detectados no lado da GPU. Em seguida, o senhor pode aumentar ou reduzir a resolução de renderização para controlar dinamicamente a quantidade de trabalho na GPU.

Durante o desenvolvimento, é possível até mesmo visualizar o tempo em um HUD do aplicativo, o que permite que o usuário tenha um mini Profiler de alto nível e em tempo real incorporado diretamente no aplicativo. Dessa forma, ele estará sempre disponível para uso.

Por fim, o senhor pode usar o Frame Timing Manager para gerar relatórios de desempenho do modo de liberação. Com base nas informações coletadas, o senhor pode enviar estatísticas aos seus servidores sobre o desempenho do seu aplicativo em diferentes plataformas para uma melhor tomada de decisão geral.

Quais medições a API do Frame Timing Manager fornece?

A API do Frame Timing Manager fornece um conjunto de medições úteis de CPU e GPU por quadro como a estrutura FrameTiming. Aqui está uma lista deles:

  • cpuFrameTime refere-se ao tempo total do quadro da CPU. Ele é calculado como o tempo entre o início do quadro e o próximo quadro no thread principal.
  • cpuMainThreadFrameTime é o tempo de trabalho do thread principal, ou o tempo total entre o início do quadro e a conclusão do trabalho do thread principal.
  • cpuRenderThreadFrameTime refere-se ao tempo de trabalho do thread de renderização ou ao tempo total entre a primeira solicitação de trabalho enviada ao thread de renderização e o momento em que a função Present() é chamada.
  • cpuMainThreadPresentWaitTime é a duração que a CPU gasta aguardando a conclusão de Present() durante o quadro.
  • gpuFrameTime é o tempo de trabalho da GPU ou a quantidade total de tempo entre o trabalho enviado à GPU e o sinal que indica que a GPU concluiu o trabalho. Consulte as limitações relevantes na seção "Plataformas suportadas e limitações" abaixo.

Observe que o cpuMainThreadPresentWaitTime é a soma dos blocos "[wait]" mostrados e inclui esperas para Present() e FPS alvo. É mais difícil mostrar o tempo de trabalho da GPU, pois ele começa em algum lugar no meio da "Renderização da cena" e termina no ponto de sincronização do próximo quadro com o quadro anterior.

Como começar

Primeiro, vale a pena observar que o Frame Timing Manager está sempre ativo nas compilações de desenvolvimento. Se o senhor planeja usá-lo apenas no desenvolvimento, não precisa concluir nenhuma etapa adicional - basta usar a API C# do Frame Timing Manager ou seus contadores.

Para compilações de versão, o senhor precisa ativar explicitamente o recurso antes de poder usá-lo. Há várias maneiras de fazer isso. Uma abordagem simples é marcar uma caixa de seleção nas configurações do Project Player. Nesse caso, o senhor pode ler dados usando a API C#. Infelizmente, porém, esse é o método menos eficiente. Se o recurso for ativado nas configurações, ele permanecerá ativo, independentemente de o usuário precisar ou não dele em um determinado momento.


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

Como alternativa, o senhor pode ler os valores do Frame Timing Manager usando a API do Profiler Recorder. A vantagem da API Profiler Recorder é que as medições do Frame Timing Manager só são feitas quando o usuário anexa um gravador ao contador, o que lhe dá controle dinâmico sobre o recurso e sua sobrecarga.

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
Detecção de gargalos

Os dados fornecidos pelo Frame Timing Manager podem ser usados para a detecção de gargalos. Na variante mais simples, o senhor pode comparar o tempo da CPU do thread principal, da CPU do thread de renderização, do Present Wait e da GPU para determinar qual é a maior e mais provável causa da limitação da taxa de quadros. Por exemplo:

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;
}
}
cs
HUD

O Frame Timing Manager pode ser usado como um simples Profiler na tela, útil para avaliar a integridade do aplicativo. Sua forma mais básica pode ser a seguinte:

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
Plataformas suportadas e limitações

O Frame Timing Manager é compatível com todas as plataformas suportadas pela Unity, com as seguintes exceções:

  • Nas plataformas Linux, quando a API OpenGL é usada, nenhum tempo de GPU é fornecido.
  • Na plataforma WebGL, nenhum tempo de GPU é fornecido.
  • No iOS e no macOS, quando a API Metal é usada, foi relatado que o tempo da GPU pode ser potencialmente maior do que o tempo total do quadro sob carga pesada da GPU.

As especificidades importantes da implementação do Frame Timing Manager são:

O Frame Timing Manager produz resultados com um atraso fixo de quatro quadros. Isso significa que o senhor obtém resultados para um quadro que está quatro quadros atrás (não para o quadro atual). O Frame Timing Manager fornece medições de tempo sincronizadas para o mesmo quadro, tanto para a CPU quanto para a GPU. Devido a limitações de plataforma e hardware, os resultados de tempo da GPU não estão disponíveis imediatamente na maioria das plataformas.

O Frame Timing Manager não garante que a GPU estará disponível para todos os quadros. A GPU pode não retornar os resultados a tempo ou não retornar nenhum resultado. Nesses casos, o tempo de quadro da GPU é relatado como zero.

Em plataformas que não permitem o registro de data e hora da GPU, a Unity calcula o valor do Frame Complete Time em vez de medi-lo. Mais especificamente, a Unity calcula o Frame Complete Time como First Submit Timestamp + tempo de GPU. Se a GPU não conseguir fornecer o tempo da GPU, o Frame Complete Time será automaticamente definido como igual ao Present Timestamp.

Nas GPUs que usam arquitetura de renderização diferida baseada em blocos, como as plataformas móveis, os resultados são menos precisos porque a execução da GPU é diferida e a execução das fases de renderização pode ser feita separadamente. O Frame Timing Manager só pode medir a duração total.

Tópicos avançados

Para usuários avançados, o Frame Timing Manager fornece informações de carimbo de data/hora que podem ser usadas para visualizar a linha do tempo do quadro ou calcular deltas com outros marcadores.

Os registros de data e hora fornecidos são:

  • frameStartTimestamp: O tempo do relógio da CPU quando o quadro é iniciado pela primeira vez
  • firstSubmitTimestamp: O tempo do relógio da CPU quando o trabalho inicial é enviado à GPU durante o quadro (dependente da plataforma e da API); plataformas diferentes enviam em momentos diferentes.
  • cpuTimePresentCalled: O tempo do relógio da CPU no momento em que Present() é chamada para o quadro. É o momento em que a Unity termina de enviar objetos para renderização e informa à GPU que o quadro pode ser apresentado ao usuário.
  • cpuTimeFrameComplete: O tempo do relógio da CPU no momento em que a GPU termina de renderizar o quadro. Na maioria das plataformas, esse valor é calculado e igual ao First Submit Timestamp + Frame GPU time.

Diga-nos o que o senhor acha

Esperamos que esses aprimoramentos ajudem o senhor a medir e entender o histórico de desempenho exclusivo do seu aplicativo. Esses benefícios estão agora em suas mãos com o Unity 2022.1.

Se o senhor estiver se perguntando o que está por vir com nossas ferramentas de criação de perfil, confira nosso roteiro aqui. Caso contrário, sinta-se à vontade para entrar em contato com a equipe em nosso fórum. Gostaríamos muito de ouvir suas opiniões e ver como podemos melhorar ainda mais os recursos e ferramentas de desempenho do Unity.