Detección de cuellos de botella de rendimiento con Unity Frame Timing Manager

Crear una experiencia destacada que funcione sin problemas en una variedad de dispositivos y plataformas puede ser un desafío. Es por eso que continuamos refinando nuestras herramientas, como nuestro Frame Timing Manager mejorado , para la optimización en todos los ámbitos. Continúe leyendo para descubrir cómo la actualización de Unity 2022.1 brinda soporte de plataforma mejorado para esta función, lo que le permite recopilar más datos que antes.

Detección de cuellos de botella de rendimiento con Unity Frame Timing Manager
Detección de cuellos de botella de rendimiento con Unity Frame Timing Manager

Crear una experiencia destacada que funcione sin problemas en una variedad de dispositivos y plataformas puede ser un desafío. Es por eso que continuamos refinando nuestras herramientas, como nuestro Frame Timing Manager mejorado , para la optimización en todos los ámbitos. Continúe leyendo para descubrir cómo la actualización de Unity 2022.1 brinda soporte de plataforma mejorado para esta función, lo que le permite recopilar más datos que antes.

¿Qué puede hacer Frame Timing Manager por usted?

Frame Timing Manager es una función que proporciona mediciones de tiempo a nivel de fotograma, como tiempos totales de CPU y GPU de fotogramas. En comparación con Unity Profiler y Profiler API de uso general, Frame Timing Manager está diseñado para una tarea muy específica y, por lo tanto, tiene una sobrecarga de rendimiento mucho menor. La cantidad de información recopilada se limita cuidadosamente, ya que solo destaca las estadísticas de fotogramas más importantes.

Una razón principal para aprovechar Frame Timing Manager es investigar los cuellos de botella de rendimiento con más detalle. Esto le permite determinar qué frena el rendimiento de su aplicación: ¿Está limitado por el subproceso principal o el subproceso de procesamiento en la CPU, o está limitado por la GPU? En función de su análisis, puede tomar más medidas para mejorar el rendimiento.

La función de resolución dinámica admite la reparación de cuellos de botella detectados en el lado de la GPU. Luego puede aumentar o reducir la resolución de renderizado para controlar dinámicamente la cantidad de trabajo en la GPU.

Durante el desarrollo, incluso puede visualizar el tiempo en el HUD de una aplicación, lo que le permite tener un mini generador de perfiles de alto nivel en tiempo real integrado en su aplicación. De esta manera, siempre estará disponible para su uso.

Por último, puede usar Frame Timing Manager para generar informes de rendimiento del modo de lanzamiento. En función de la información recopilada, puede enviar estadísticas a sus servidores sobre el rendimiento de su aplicación en diferentes plataformas para una mejor toma de decisiones en general.

¿Qué medidas proporciona la API de Frame Timing Manager?

La API de Frame Timing Manager proporciona un conjunto de medidas útiles de CPU y GPU por cuadro como la estructura FrameTiming. Aquí hay una lista de ellos:

  • cpuFrameTime se refiere al tiempo total de fotogramas de la CPU. Se calcula como el tiempo entre el inicio del cuadro y el siguiente cuadro en el subproceso principal.
  • cpuMainThreadFrameTime es el tiempo de trabajo del subproceso principal, o la cantidad total de tiempo entre el inicio del marco y el final del trabajo del subproceso principal.
  • cpuRenderThreadFrameTime se refiere al tiempo de trabajo del subproceso de representación, o la cantidad total de tiempo entre la primera solicitud de trabajo enviada al subproceso de representación y el momento en que se llama a la función Present().
  • cpuMainThreadPresentWaitTime es el tiempo que la CPU pasa esperando que Present() se complete durante el marco.
  • gpuFrameTime es el tiempo de trabajo de la GPU, o la cantidad total de tiempo entre el trabajo enviado a la GPU y la señal que indica que la GPU ha terminado el trabajo. Consulte las limitaciones relevantes en la sección "Plataformas admitidas y limitaciones" a continuación.

Tenga en cuenta que cpuMainThreadPresentWaitTime es la suma de los bloques "[espera]" mostrados e incluye esperas para Present() y fps objetivo. Es más difícil mostrar el tiempo de trabajo de la GPU, ya que comienza en algún lugar a la mitad de la "Representación de escena" y termina en el punto de sincronización del siguiente cuadro con el cuadro anterior.

Cómo empezar

Primero, vale la pena señalar que Frame Timing Manager siempre está activo en las compilaciones de desarrollo. Si planea usarlo solo en desarrollo, no necesita completar ningún paso adicional; solo puede usar la API C# de Frame Timing Manager o sus contadores.

Para las compilaciones de lanzamiento, debe activar explícitamente la función antes de poder usarla. Hay varias maneras de hacer esto. Un enfoque sencillo es marcar una casilla de verificación en la configuración de Project Player. En este caso, puede leer datos mediante la API de C#. Desafortunadamente, sin embargo, es el método menos eficiente. Si habilita la función en la configuración, permanecerá activa ya sea que la necesite o no en un momento específico.

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
        }

             }

         }

Como alternativa, puede leer los valores de Frame Timing Manager mediante la API de Profiler Recorder. El beneficio de Profiler Recorder API es que las mediciones de Frame Timing Manager solo se toman cuando conecta una grabadora al contador, lo que le brinda un control dinámico sobre la función y su 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
    }

         }

Detección de cuellos de botella

Los datos proporcionados por Frame Timing Manager se pueden utilizar para la detección de cuellos de botella. En la variante más simple, puede comparar la CPU del subproceso principal, la CPU del subproceso de procesamiento, la espera presente y el tiempo de GPU para determinar cuál es la causa más grande y más probable de la limitación de la velocidad de fotogramas. Por ejemplo:

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

         }

HUD

Frame Timing Manager se puede utilizar como un perfilador en pantalla simple, útil para evaluar el estado de la aplicación. Su forma más básica podría verse así:

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);
    }

         }

Plataformas compatibles y limitaciones

Frame Timing Manager es compatible con todas las plataformas compatibles con Unity, con las siguientes excepciones:

  • En las plataformas Linux, cuando se utiliza la API de OpenGL, no se proporciona tiempo de GPU.
  • En la plataforma WebGL, no se proporciona tiempo de GPU.
  • En iOS y macOS, cuando se usa la API de Metal, se ha informado que el tiempo de GPU podría ser potencialmente mayor que el tiempo total de cuadros bajo una carga pesada de GPU.

Los detalles de implementación importantes del Frame Timing Manager son:

  1. Frame Timing Manager produce resultados con un retraso fijo de cuatro fotogramas. Esto significa que obtiene resultados para un cuadro que está cuatro cuadros por detrás (no para el cuadro actual). Frame Timing Manager proporciona mediciones de tiempo sincronizadas para el mismo cuadro tanto para CPU como para GPU. Debido a las limitaciones de la plataforma y el hardware, los resultados de temporización de la GPU no están disponibles de inmediato en la mayoría de las plataformas.
  2. Frame Timing Manager no garantiza que la GPU estará disponible para todos los fotogramas. Es posible que la GPU no devuelva los resultados a tiempo o que no devuelva ningún resultado. En estos casos, el tiempo de cuadro de GPU se informa como cero.
  3. En las plataformas que no permiten la marca de tiempo de la GPU, Unity calcula el valor del tiempo de finalización del cuadro en lugar de medirlo. Más específicamente, Unity calcula el tiempo de finalización del cuadro como la marca de tiempo del primer envío + el tiempo de GPU. Si la GPU no proporciona el tiempo de GPU, Frame Complete Time se establece automáticamente como igual a Present Timestamp.
  4. En las GPU que utilizan una arquitectura de representación diferida basada en mosaicos, como las plataformas móviles, los resultados son menos precisos porque la ejecución de la GPU se difiere y la ejecución de las fases de representación se puede realizar por separado. Frame Timing Manager solo puede medir la duración total.

Temas avanzados

Para usuarios avanzados, Frame Timing Manager proporciona información de marca de tiempo que se puede utilizar para visualizar la línea de tiempo de fotogramas o calcular deltas con otros marcadores.

Las marcas de tiempo proporcionadas son:

  • frameStartTimestamp : la hora del reloj de la CPU cuando se inicia el marco por primera vez
  • firstSubmitTimestamp : la hora del reloj de la CPU cuando el trabajo inicial se envía a la GPU durante el marco (depende de la plataforma y la API); diferentes plataformas envían en diferentes momentos.
  • cpuTimePresentCalled : la hora del reloj de la CPU en el punto Present() se llama para el marco. Es el momento en que Unity termina de enviar objetos para renderizar e informa a la GPU que el marco se puede presentar al usuario.
  • cpuTimeFrameComplete : la hora del reloj de la CPU en el momento en que la GPU termina de procesar el marco. En la mayoría de las plataformas, este valor se calcula y equivale a la marca de tiempo del primer envío + el tiempo de GPU del cuadro.

Háganos saber lo que piensas

Esperamos que estas mejoras lo ayuden a medir y comprender la historia de rendimiento única de su aplicación. Estos beneficios ahora están en tus manos con Unity 2022.1 .

Si se pregunta qué sigue para nuestras herramientas de creación de perfiles, consulte nuestra hoja de ruta aquí . De lo contrario, no dude en comunicarse con el equipo en nuestro foro . Nos encantaría escuchar sus opiniones y ver cómo podemos mejorar aún más las funciones y las herramientas de rendimiento de Unity.