¿Qué estás buscando?
Hero background image

Consejos sobre perfiles de rendimiento para desarrolladores de juegos

Un rendimiento fluido es esencial para crear experiencias de juego envolventes para los jugadores. Al perfilar y perfeccionar el rendimiento de su juego para una amplia gama de plataformas y dispositivos, puede ampliar su base de jugadores y aumentar sus posibilidades de éxito.

Esta página describe un flujo de trabajo general de creación de perfiles para desarrolladores de juegos. Es un extracto del libro electrónico Ultimate guide to profiling Unity games, que puede descargarse gratuitamente. El libro electrónico ha sido creado por expertos externos e internos de Unity en desarrollo, creación de perfiles y optimización de juegos.

Sigue leyendo para conocer los objetivos útiles que se pueden establecer con la creación de perfiles, los cuellos de botella de rendimiento más comunes, como los que se producen en la CPU o en la GPU, y cómo identificar e investigar estas situaciones con más detalle.

Gráfico de la frecuencia de imagen de FPS
Establecer un presupuesto marco

Medir la tasa de fotogramas por segundo (fps) de un juego no es lo ideal para ofrecer una experiencia coherente a los jugadores. Consideremos el siguiente escenario simplificado:

Durante el tiempo de ejecución, tu juego renderiza 59 fotogramas en 0,75 segundos. Sin embargo, el siguiente fotograma tarda 0,25 segundos en renderizarse. La tasa media de fotogramas por segundo de 60 fps suena bien, pero en realidad los jugadores notarán un efecto de tartamudeo, ya que el último fotograma tarda un cuarto de segundo en renderizarse.

Esta es una de las razones por las que es importante fijarse un presupuesto de tiempo específico por fotograma. Esto le proporciona un objetivo sólido sobre el que trabajar a la hora de perfilar y optimizar su juego y, en última instancia, crea una experiencia más fluida y coherente para sus jugadores.

Cada fotograma tendrá un presupuesto de tiempo basado en los fps objetivo. Una aplicación orientada a 30 fps debería tardar siempre menos de 33,33 ms por fotograma (1000 ms / 30 fps). Del mismo modo, un objetivo de 60 fps deja 16,66 ms por fotograma (1000 ms / 60 fps).

Puedes superar este presupuesto durante secuencias no interactivas, por ejemplo, al mostrar menús de IU o al cargar escenas, pero no durante el juego. Incluso un solo fotograma que supere el presupuesto de fotogramas objetivo provocará problemas.

*NOTA*: Una frecuencia de imagen alta y constante en los juegos de RV es esencial para evitar causar náuseas o molestias a los jugadores. Sin ella, te arriesgas a que el titular de la plataforma rechace tu juego durante la certificación.

Fotogramas por segundo: Una métrica engañosa

Una forma habitual en que los jugadores miden el rendimiento es con la tasa de fotogramas, o fotogramas por segundo. Sin embargo, se recomienda utilizar el tiempo de fotograma en milisegundos. Para entender por qué, mira el gráfico anterior de fps frente a tiempo de fotogramas.

Considera estas cifras:

1000 ms/seg / 900 fps = 1,111 ms por fotograma
1000 ms/seg / 450 fps = 2,222 ms por fotograma

1000 ms/seg / 60 fps = 16,666 ms por fotograma
1000 ms/seg / 56,25 fps = 17,777 ms por fotograma

Si tu aplicación funciona a 900 fps, esto se traduce en un tiempo de fotograma de 1,111 milisegundos por fotograma. A 450 fps, son 2,222 milisegundos por fotograma. Esto representa una diferencia de sólo 1,111 milisegundos por fotograma, aunque la velocidad de fotogramas parece reducirse a la mitad.

Si te fijas en las diferencias entre 60 fps y 56,25 fps, eso se traduce en 16,666 milisegundos por fotograma y 17,777 milisegundos por fotograma, respectivamente. Esto también representa 1,111 milisegundos más por fotograma, pero en este caso, la caída de la velocidad de fotogramas es mucho menos drástica porcentualmente.

Por eso los desarrolladores utilizan el tiempo medio de fotogramas para evaluar la velocidad del juego en lugar de los fps.

No te preocupes por los fps a menos que caigas por debajo de tu tasa de fotogramas objetivo. Concéntrate en el tiempo de fotogramas para medir la velocidad a la que se ejecuta el juego y, a continuación, mantente dentro de tu presupuesto de fotogramas.

Lea el artículo original, "Robert Dunlop's fps versus frame time", para más información.

Desafíos móviles
Desafíos móviles

El control térmico es una de las áreas más importantes que hay que optimizar al desarrollar aplicaciones para dispositivos móviles. Si la CPU o la GPU pasan demasiado tiempo trabajando a pleno rendimiento debido a un diseño ineficiente, esos chips se calentarán. Para evitar daños en los chips (¡y posibles quemaduras en las manos del jugador!), el sistema operativo reducirá la velocidad de reloj del dispositivo para permitir que se enfríe, lo que provocará tartamudeos en los fotogramas y una mala experiencia para el usuario. Esta reducción del rendimiento se conoce como estrangulamiento térmico.

Una mayor frecuencia de fotogramas y un aumento de la ejecución de código (o de las operaciones de acceso a la DRAM) provocan un aumento del consumo de batería y de la generación de calor. Un mal rendimiento también puede dejar fuera a segmentos enteros de dispositivos móviles de gama baja, lo que puede hacer que se pierdan oportunidades de mercado y, por tanto, se reduzcan las ventas.

Cuando te enfrentes al problema de las térmicas, considera el presupuesto con el que tienes que trabajar como un presupuesto para todo el sistema.

Combate la ralentización térmica y el agotamiento de la batería aprovechando una técnica de creación de perfiles temprana para optimizar tu juego desde el principio. Ajusta la configuración del proyecto al hardware de tu plataforma de destino para combatir los problemas térmicos y de agotamiento de la batería.

Ajustar los presupuestos de fotogramas en el móvil

Dejar un tiempo de inactividad de los fotogramas de alrededor del 35% es la recomendación típica para combatir los problemas térmicos del dispositivo durante tiempos de juego prolongados. Esto da tiempo a los chips móviles a enfriarse y ayuda a evitar un consumo excesivo de batería. Utilizando un tiempo de fotograma objetivo de 33,33 ms por fotograma (para 30 fps), un presupuesto de fotograma típico para dispositivos móviles será de aproximadamente 22 ms por fotograma.

El cálculo es el siguiente: (1000 ms / 30) * 0,65 = 21,66 ms

Para alcanzar 60 fps en móvil utilizando el mismo cálculo se necesitaría un tiempo de fotograma objetivo de (1000 ms / 60) * 0,65 = 10,83 ms. Esto es difícil de conseguir en muchos dispositivos móviles y agotaría la batería el doble de rápido que el objetivo de 30 fps. Por estas razones, la mayoría de los juegos para móviles tienen un objetivo de 30 fps en lugar de 60. Utilice Application.targetFrameRate para controlar este ajuste, y consulte la sección "Establecer un presupuesto de fotogramas" del libro electrónico para obtener más detalles sobre el tiempo de fotogramas.

El escalado de frecuencias en los chips móviles puede dificultar la identificación de las asignaciones presupuestarias de tiempo de inactividad de los fotogramas al crear perfiles. Sus mejoras y optimizaciones pueden tener un efecto positivo neto, pero el dispositivo móvil podría estar reduciendo la frecuencia y, como resultado, funcionando más frío. Utilice herramientas personalizadas como FTrace o Perfetto para supervisar las frecuencias de los chips móviles, el tiempo de inactividad y el escalado antes y después de las optimizaciones.

Mientras te mantengas dentro de tu presupuesto total de tiempo de fotogramas para tu objetivo de fps (33,33 ms para 30 fps) y veas que tu dispositivo trabaja menos o registra temperaturas más bajas para mantener esta velocidad de fotogramas, entonces vas por buen camino.

Otra razón para dar un respiro al presupuesto de fotogramas en los dispositivos móviles es tener en cuenta las fluctuaciones de temperatura en el mundo real. En un día caluroso, un dispositivo móvil se calentará y tendrá problemas para disipar el calor, lo que puede provocar una ralentización térmica y un rendimiento deficiente de los juegos. Reservar un porcentaje del presupuesto del marco ayudará a evitar este tipo de situaciones.

Reduce las operaciones de acceso a la memoria
Reduce las operaciones de acceso a la memoria

El acceso a la DRAM es una operación que consume mucha energía en los dispositivos móviles. Según los consejos de optimización de Arm para contenidos gráficos en dispositivos móviles, el acceso a la memoria LPDDR4 cuesta aproximadamente 100 picojulios por byte.

Reducir el número de operaciones de acceso a memoria por fotograma en:

  • Reducción de la frecuencia de imagen
  • Reducir la resolución de la pantalla siempre que sea posible
  • Uso de mallas más sencillas con menor número de vértices y precisión de atributos
  • Uso de la compresión de texturas y el mipmapping

Cuando necesites centrarte en dispositivos que utilicen hardware Arm o Arm Mali, las herramientas de Arm Mobile Studio (en concreto, Streamline Performance Analyzer) incluyen algunos contadores de rendimiento excelentes para identificar problemas de ancho de banda de memoria. Los contadores se enumeran y explican para cada generación de GPU Arm, por ejemplo, Mali-G78. Tenga en cuenta que el perfilado de GPU de Mobile Studio requiere Arm Mali.

Establecer niveles de hardware para la evaluación comparativa

Además de utilizar herramientas de creación de perfiles específicas para cada plataforma, establezca niveles o un dispositivo con las especificaciones más bajas para cada plataforma y nivel de calidad que desee admitir y, a continuación, cree perfiles y optimice el rendimiento para cada una de estas especificaciones.

Por ejemplo, si se dirige a plataformas móviles, puede decidir ofrecer tres niveles con controles de calidad que activen o desactiven funciones en función del hardware de destino. A continuación, se optimiza para la especificación de dispositivo más baja de cada nivel. Como otro ejemplo, si estás desarrollando un juego tanto para PlayStation 4 como para PlayStation 5, asegúrate de crear perfiles en ambas.

Para una guía completa de optimización para móviles, eche un vistazo a Optimice el rendimiento de sus juegos para móviles. Este libro electrónico contiene muchos consejos y trucos que te ayudarán a reducir la ralentización térmica y aumentar la duración de la batería de los dispositivos móviles que ejecuten tus juegos.

De los perfiles de alto a los de bajo nivel

Un enfoque de arriba a abajo funciona bien cuando se crean perfiles, comenzando con Deep Profiling desactivado. Utilice este enfoque de alto nivel para recopilar datos y tomar notas sobre qué escenarios provocan asignaciones gestionadas no deseadas o demasiado tiempo de CPU en las áreas del bucle central del juego.

Primero tendrás que reunir las pilas de llamadas para los marcadores GC.Alloc. Si no está familiarizado con este proceso, encontrará algunos consejos y trucos en la sección "Localización de asignaciones de memoria recurrentes a lo largo de la vida útil de la aplicación" en Guía definitiva para la creación de perfiles de juegos Unity.

Si las pilas de llamadas informadas no son lo suficientemente detalladas como para rastrear el origen de las asignaciones u otras ralentizaciones, puede realizar una segunda sesión de creación de perfiles con Deep Profiling activado para encontrar el origen de las asignaciones.

Cuando recoja notas sobre los "delincuentes" del tiempo del marco, asegúrese de anotar cómo se comparan en relación con el resto del marco. Este impacto relativo se verá afectado al activar Perfiles Profundos.

Más información sobre la creación de perfiles profundos en Guía definitiva para crear perfiles de juegos Unity.

Perfil temprano

Los mejores beneficios de la creación de perfiles se obtienen cuando se empieza en una fase temprana del ciclo de desarrollo del proyecto.

Perfile pronto y a menudo para que usted y su equipo comprendan y memoricen una "firma de rendimiento" para el proyecto. Si el rendimiento cae en picado, podrás detectar fácilmente cuándo las cosas van mal y poner remedio al problema.

Los resultados de perfilado más precisos siempre se obtienen ejecutando y perfilando compilaciones en dispositivos de destino, junto con el aprovechamiento de herramientas específicas de la plataforma para profundizar en las características de hardware de cada plataforma. Esta combinación le proporcionará una visión holística del rendimiento de las aplicaciones en todos los dispositivos de destino.

Diagrama de flujo para la generación de perfiles
Identifica problemas de rendimiento

Descargue aquí la versión imprimible en PDF de este gráfico.

En algunas plataformas, es fácil determinar si la aplicación está vinculada a la CPU o a la GPU. Por ejemplo, al ejecutar un juego de iOS desde Xcode, el panel de fps muestra un gráfico de barras con el tiempo total de CPU y GPU para que puedas ver cuál es el más alto. El tiempo de CPU incluye el tiempo de espera de VSync, que siempre está activado en los dispositivos móviles.

Sin embargo, en algunas plataformas puede resultar complicado obtener datos de temporización de la GPU. Afortunadamente, Unity Profiler muestra suficiente información para identificar la ubicación de los cuellos de botella de rendimiento. El diagrama de flujo anterior ilustra el proceso inicial de elaboración de perfiles, y las secciones siguientes ofrecen información detallada sobre cada paso. También presentan capturas de Profiler de proyectos reales de Unity para ilustrar el tipo de cosas que hay que buscar.

Para obtener una visión global de toda la actividad de la CPU, incluso cuando está esperando a la GPU, utiliza la vista de línea de tiempo en el módulo CPU del Profiler. Familiarícese con los marcadores comunes de Profiler para interpretar correctamente las capturas. Algunos de los marcadores de Profiler pueden aparecer de forma diferente en función de la plataforma de destino, así que dedica tiempo a explorar las capturas de tu juego en cada una de las plataformas de destino para hacerte una idea de cómo es una captura "normal" para tu proyecto.

El rendimiento de un proyecto está limitado por el chip y/o el hilo que más tarda. Esa es el área en la que debe centrar sus esfuerzos de optimización. Por ejemplo, imagina un juego con un tiempo de fotogramas objetivo de 33,33 ms y VSync activado:

  • Si el tiempo de fotogramas de la CPU (sin VSync) es de 25 ms y el de la GPU de 20 ms, ¡no hay problema! Estás atado a la CPU, pero todo está dentro del presupuesto, y optimizar cosas no mejorará la tasa de fotogramas (a menos que consigas que tanto la CPU como la GPU bajen de 16,66 ms y salten a 60 fps).
  • Si el tiempo de fotograma de la CPU es de 40 ms y el de la GPU es de 20 ms, estás limitado por la CPU y tendrás que optimizar el rendimiento de la CPU. Optimizar el rendimiento de la GPU no ayudará; de hecho, es posible que quieras trasladar parte del trabajo de la CPU a la GPU, por ejemplo, utilizando sombreadores de cálculo en lugar de código C# para algunas cosas, con el fin de equilibrar las cosas.
  • Si el tiempo de fotograma de la CPU es de 20 ms y el de la GPU es de 40 ms, estás ligado a la GPU y necesitas optimizar el trabajo de la GPU.
  • Si la CPU y la GPU están ambas a 40 ms, estás limitado por ambas y tendrás que optimizarlas por debajo de 33,33 ms para alcanzar los 30 fps.

Consulte estos recursos que analizan con más detalle el hecho de estar vinculado a la CPU o a la GPU:

¿Se ajusta a su presupuesto?
¿Se ajusta a su presupuesto?

La creación de perfiles y la optimización de tu proyecto desde el principio y con frecuencia a lo largo del desarrollo te ayudarán a garantizar que todos los subprocesos de CPU de tu aplicación y el tiempo total de fotogramas de la GPU se ajusten al presupuesto de fotogramas.

Arriba se muestra una imagen de una captura de Profiler de un juego Unity para móviles desarrollado por un equipo que realizó un perfilado y optimización continuos. El juego alcanza los 60 fps en móviles de altas especificaciones, y los 30 fps en móviles de especificaciones medias/bajas, como el de esta captura.

Observe cómo casi la mitad del tiempo del fotograma seleccionado está ocupado por el marcador amarillo WaitForTargetfps del Profiler. La aplicación ha establecido Application.targetFrameRate en 30 fps, y VSync está activado. El trabajo de procesamiento real en el hilo principal finaliza en torno a los 19 ms, y el resto del tiempo se dedica a esperar a que transcurran los 33,33 ms restantes antes de comenzar el siguiente fotograma. Aunque este tiempo se representa con un marcador de Profiler, el hilo principal de la CPU está esencialmente inactivo durante este tiempo, permitiendo que la CPU se enfríe y utilizando un mínimo de energía de la batería.

El marcador a tener en cuenta puede ser diferente en otras plataformas o si VSync está desactivado. Lo importante es comprobar si el hilo principal se está ejecutando dentro de su presupuesto de fotogramas o exactamente en su presupuesto de fotogramas, con algún tipo de marcador que indique que la aplicación está esperando VSync y si los otros hilos tienen algún tiempo de inactividad.

El tiempo de inactividad se representa mediante marcadores Profiler grises o amarillos. La captura de pantalla anterior muestra que el hilo de renderizado está inactivo en Gfx.WaitForGfxCommandsFromMainThread, lo que indica momentos en los que ha terminado de enviar llamadas de dibujo a la GPU en un fotograma y está esperando más peticiones de llamadas de dibujo de la CPU en el siguiente. Del mismo modo, aunque el hilo Job Worker 0 pasa algún tiempo en Canvas.GeometryJob, la mayor parte del tiempo está inactivo. Todos estos son signos de una aplicación que se ajusta cómodamente al presupuesto del marco.

Si su juego tiene un presupuesto

Si estás dentro del presupuesto de fotogramas, incluidos los ajustes realizados en el presupuesto para tener en cuenta el uso de la batería y el estrangulamiento térmico, has terminado la creación de perfiles de rendimiento hasta la próxima vez: enhorabuena. Considere la posibilidad de ejecutar el Memory Profiler para asegurarse de que la aplicación también está dentro de su presupuesto de memoria.

La imagen de arriba muestra un juego que se ejecuta cómodamente dentro del presupuesto de fotogramas de ~22 ms necesario para 30 fps. Observe que WaitForTargetfps rellena el tiempo del hilo principal hasta VSync y los tiempos grises de inactividad en el hilo de renderizado y el hilo trabajador. Tenga en cuenta también que el intervalo VBlank puede observarse mirando los tiempos finales de Gfx.Present fotograma a fotograma, y que puede dibujar una escala de tiempo en el área Línea de tiempo o en la regla de tiempo de la parte superior para medir de uno de ellos al siguiente.

CPU limitada
CPU-bound

Si tu juego no está dentro del presupuesto de fotogramas de la CPU, el siguiente paso es investigar qué parte de la CPU es el cuello de botella, es decir, qué subproceso es el más ocupado. El objetivo de la creación de perfiles es identificar los cuellos de botella como objetivos de optimización; si te basas en conjeturas, puedes acabar optimizando partes del juego que no son cuellos de botella, con lo que la mejora del rendimiento general será escasa o nula. Algunas "optimizaciones" pueden incluso empeorar el rendimiento general del juego.

Es raro que toda la carga de trabajo de la CPU sea el cuello de botella. Las CPU modernas tienen varios núcleos diferentes, capaces de realizar trabajos de forma independiente y simultánea. En cada núcleo de la CPU pueden ejecutarse distintos subprocesos. Una aplicación Unity completa utiliza una serie de hilos para diferentes propósitos, pero los hilos que son los más comunes para encontrar problemas de rendimiento son:

  • El hilo principal: Aquí es donde toda la lógica/scripts del juego realizan su trabajo por defecto y donde se invierte la mayor parte del tiempo para características y sistemas como la física, la animación, la interfaz de usuario y el renderizado.
  • El hilo de renderizado: Durante el proceso de renderizado, el subproceso principal examina la escena y lleva a cabo la selección de la cámara, la clasificación de la profundidad y el procesamiento por lotes de las llamadas de dibujo, lo que da como resultado una lista de cosas para renderizar. Esta lista se pasa al hilo de renderizado, que la traduce de la representación interna agnóstica de plataformas de Unity a las llamadas específicas de la API de gráficos necesarias para instruir a la GPU en una plataforma concreta.
  • Los hilos de los trabajadores de Job: Los desarrolladores pueden hacer uso del sistema de trabajos de C# para programar determinados tipos de trabajo que se ejecutarán en subprocesos de trabajo, lo que reduce la carga de trabajo del subproceso principal. Algunos de los sistemas y características de Unity también hacen uso del sistema de trabajos, como la física, la animación y el renderizado.

Tema principal

La imagen de arriba muestra cómo se verían las cosas en un proyecto vinculado al hilo principal. Este proyecto se está ejecutando en una Meta Quest 2, que normalmente tiene unos presupuestos de fotogramas de 13,88 ms (72 fps) o incluso 8,33 ms (120 fps), porque las altas frecuencias de fotogramas son importantes para evitar el mareo en los dispositivos de RV. Sin embargo, aunque este juego tuviera como objetivo los 30 fps, está claro que este proyecto tiene problemas.

Aunque el hilo de renderizado y los hilos de trabajo tienen un aspecto similar al del ejemplo que está dentro del presupuesto del fotograma, el hilo principal está claramente ocupado con trabajo durante todo el fotograma. Incluso teniendo en cuenta la pequeña cantidad de sobrecarga de Profiler al final del fotograma, el hilo principal está ocupado durante más de 45 ms, lo que significa que este proyecto alcanza frecuencias de fotogramas inferiores a 22 fps. No hay ningún marcador que muestre el hilo principal ocioso esperando VSync; está ocupado durante todo el fotograma.

La siguiente fase de la investigación consiste en identificar las partes del marco que requieren más tiempo y entender a qué se debe. En este fotograma, PostLateUpdate.FinishFrameRendering tarda 16,23 ms, más que todo el presupuesto del fotograma. Una inspección más cercana revela que hay cinco instancias de un marcador llamado Inl_RenderCameraStack, indicando que hay cinco cámaras activas y renderizando la escena. Dado que cada cámara en Unity invoca todo el proceso de renderizado, incluyendo la selección, clasificación y procesamiento por lotes, la tarea de mayor prioridad para este proyecto es reducir el número de cámaras activas, idealmente a una sola.

BehaviourUpdate, el marcador que engloba todos los métodos MonoBehaviour Update(), tarda 7,27 ms, y las secciones magenta de la línea de tiempo indican dónde los scripts asignan memoria heap gestionada. Si se cambia a la vista de jerarquía y se filtra escribiendo GC.Alloc en la barra de búsqueda, se observa que la asignación de esta memoria tarda unos 0,33 ms en este fotograma. Sin embargo, esa es una medida inexacta del impacto que tienen las asignaciones de memoria en el rendimiento de tu CPU.

Los marcadores GC.Alloc no se cronometran realmente midiendo el tiempo desde un punto de Inicio hasta un punto de Fin. Para mantener su sobrecarga reducida, se registran sólo como su marca de tiempo Begin, más el tamaño de su asignación. El Perfilador les asigna un tiempo mínimo para asegurarse de que son visibles. La asignación real puede llevar más tiempo, especialmente si es necesario solicitar al sistema un nuevo rango de memoria. Para ver el impacto con mayor claridad, coloque marcadores de Profiler alrededor del código que realiza la asignación y, en el perfilado profundo, los espacios entre las muestras GC.Alloc de color magenta en la vista de línea de tiempo proporcionan alguna indicación de cuánto tiempo pueden haber tardado.

Además, la asignación de nueva memoria puede tener efectos negativos sobre el rendimiento que son más difíciles de medir y atribuirles directamente:

  • La solicitud de nueva memoria al sistema puede afectar al presupuesto de energía de un dispositivo móvil, lo que podría provocar que el sistema ralentizara la CPU o la GPU.
  • Es probable que la nueva memoria tenga que cargarse en la caché L1 de la CPU y, por tanto, expulse las líneas de caché existentes.
  • La Recolección de Basura Incremental o Sincrónica puede ser activada directamente o con un retardo a medida que el espacio libre existente en la Memoria Administrada es eventualmente excedido.

Al principio del fotograma, cuatro instancias de Physics.FixedUpdate suman 4,57 ms. Más tarde, LateBehaviourUpdate (llamadas a MonoBehaviour.LateUpdate()) tardan 4 ms, y los Animators suponen alrededor de 1 ms.

Para garantizar que este proyecto alcance el presupuesto y la tasa de fotogramas deseados, es necesario investigar todos estos problemas de los hilos principales para encontrar las optimizaciones adecuadas. Las mayores ganancias de rendimiento se obtendrán optimizando las cosas que llevan más tiempo.

Las siguientes áreas son a menudo lugares fructíferos para buscar la optimización en los proyectos que están vinculados hilo principal:

  • Física
  • Actualizaciones del script MonoBehaviour
  • Asignación y/o recogida de basura
  • Recorte y renderizado de cámaras
  • Mala dosificación de las llamadas al sorteo
  • Actualizaciones, diseños y reconstrucciones de la interfaz de usuario
  • Animación

Dependiendo del tema que quieras investigar, también pueden ser útiles otras herramientas:

  • Para los scripts MonoBehaviour que tardan mucho pero no muestran exactamente por qué es así, añada marcadores Profiler al código o pruebe a realizar un perfilado profundo para ver la pila de llamadas completa.
  • Para los scripts que asignan memoria gestionada, active las pilas de llamadas de asignación para ver exactamente de dónde proceden las asignaciones. Como alternativa, active Deep Profiling o utilice Project Auditor, que muestra los problemas de código filtrados por memoria, para que pueda identificar todas las líneas de código que dan lugar a asignaciones gestionadas.
  • Utilice el depurador de tramas para investigar las causas de una mala dosificación de las llamadas de dibujo.

Si quieres consejos completos para optimizar tu juego, descárgate gratis estas guías de expertos en Unity:

Limitado por la CPU: Hilo de renderizado
Limitado por la CPU: Hilo de renderizado

La captura de pantalla anterior es de un proyecto que está vinculado por su hilo de renderizado. Se trata de un juego de consola con un punto de vista isométrico y un presupuesto de fotogramas objetivo de 33,33 ms.

La captura del Profiler muestra que antes de que el renderizado pueda comenzar en el fotograma actual, el hilo principal espera al hilo de renderizado, como indica el marcador Gfx.WaitForPresentOnGfxThreadmarker. El hilo de renderizado todavía está enviando comandos de llamada de dibujo del fotograma anterior y no está listo para aceptar nuevas llamadas de dibujo del hilo principal; el hilo de renderizado está pasando tiempo en Camera.Render.

Puede diferenciar entre los marcadores relativos al fotograma actual y los marcadores de otros fotogramas, porque estos últimos aparecen más oscuros. También puedes ver que una vez que el hilo principal es capaz de continuar y empezar a emitir llamadas de dibujo para que el hilo de render las procese, el hilo de render tarda más de 100 ms en procesar el fotograma actual, lo que también crea un cuello de botella durante el siguiente fotograma.

Una investigación más profunda demostró que este juego tenía una compleja configuración de renderizado, con nueve cámaras diferentes y muchas pasadas extra causadas por shaders de sustitución. El juego también estaba renderizando más de 130 luces puntuales utilizando una ruta de renderizado hacia delante, lo que puede añadir múltiples llamadas de dibujo transparentes adicionales para cada luz. En total, estos problemas se combinaron para crear más de 3000 llamadas de dibujo por fotograma.

Las siguientes son causas comunes a investigar para los proyectos que se rinden por hilos:

  • Mala dosificación de las llamadas de dibujo, sobre todo en API gráficas antiguas como OpenGL o DirectX 11.
  • Demasiadas cámaras. A menos que estés haciendo un juego multijugador a pantalla partida, lo más probable es que sólo tengas una Cámara activa.
  • Mala selección, lo que hace que se saquen demasiadas cosas. Investiga las dimensiones del frustum de tu cámara y las máscaras de capa. Considere la posibilidad de activar la eliminación de oclusiones. Quizás incluso puedas crear tu propio sistema de oclusión basado en lo que sabes sobre la disposición de los objetos en tu mundo. Observe cuántos objetos de sombra hay en la escena: la eliminación de sombras se realiza en una pasada distinta de la eliminación "normal".

El módulo Rendering Profiler muestra un resumen del número de lotes de llamadas a dibujo y llamadas a SetPass en cada fotograma. La mejor herramienta para investigar qué lotes de llamadas de dibujo está enviando el subproceso de renderizado a la GPU es el depurador de fotogramas.

Limitado por la CPU: Hilos de trabajo
Limitado por la CPU: Hilos de trabajo

Los proyectos vinculados a subprocesos de CPU distintos de los subprocesos principales o de renderizado no son tan comunes. Sin embargo, puede surgir si su proyecto utiliza la pila tecnológica orientada a datos (DOTS), especialmente si el trabajo se desplaza del subproceso principal a subprocesos de trabajo utilizando el sistema de trabajos de C#.

La captura que se ve arriba es del modo Play en el Editor, mostrando un proyecto DOTS ejecutando una simulación de fluidos de partículas en la CPU.

A primera vista parece un éxito. Los subprocesos de los trabajadores están repletos de trabajos compilados en ráfaga, lo que indica que se ha desplazado una gran cantidad de trabajo del subproceso principal. Suele ser una decisión acertada.

Sin embargo, en este caso, el tiempo de cuadro de 48,14 ms y el marcador gris WaitForJobGroupID de 35,57 ms en el hilo principal, son señales de que no todo va bien. WaitForJobGroupID indica que el subproceso principal ha programado trabajos para que se ejecuten de forma asíncrona en los subprocesos de los trabajadores, pero necesita los resultados de esos trabajos antes de que los subprocesos de los trabajadores hayan terminado de ejecutarlos. Los marcadores azules de Profiler debajo de WaitForJobGroupID muestran el hilo principal ejecutando trabajos mientras espera, en un intento de asegurar que los trabajos terminen antes.

Aunque los trabajos están compilados por Burst, siguen haciendo mucho trabajo. Quizás la estructura de consulta espacial utilizada por este proyecto para encontrar rápidamente qué partículas están cerca unas de otras debería optimizarse o cambiarse por una estructura más eficiente. O bien, los trabajos de consulta espacial pueden programarse para el final del fotograma en lugar del inicio, sin que los resultados sean necesarios hasta el inicio del fotograma siguiente. Quizá este proyecto intente simular demasiadas partículas. Para encontrar la solución es necesario analizar más a fondo el código de los trabajos, por lo que añadir marcadores de Profiler más finos puede ayudar a identificar sus partes más lentas.

Es posible que los trabajos de tu proyecto no estén tan paralelizados como en este ejemplo. Tal vez sólo tengas un trabajo largo ejecutándose en un único hilo de trabajo. Esto está bien, siempre y cuando el tiempo entre el trabajo que se programa y el momento en que debe completarse sea lo suficientemente largo para que el trabajo se ejecute. Si no es así, verás que el hilo principal se detiene mientras espera a que se complete el trabajo, como en la captura de pantalla anterior.

Las causas más comunes de puntos de sincronización y cuellos de botella en los hilos de trabajo incluyen:

  • Trabajos no compilados por el compilador Burst
  • Los trabajos de larga duración se ejecutan en un único subproceso en lugar de paralelizarse en varios subprocesos.
  • Tiempo insuficiente entre el momento en que se programa un trabajo y el momento en que se necesita el resultado.
  • Múltiples "puntos de sincronización" en un fotograma, que requieren que todos los trabajos se completen inmediatamente

Puede utilizar la función Eventos de flujo en la vista de línea de tiempo del módulo CPU Usage Profiler para investigar cuándo se programan los trabajos y cuándo el subproceso principal espera sus resultados. Para obtener más información sobre cómo escribir código DOTS eficiente, consulte el documento Mejores prácticas DOTS de DOTS.

Dependiente de la GPU
Dependiente de la GPU

Tu aplicación está ligada a la GPU si el hilo principal pasa mucho tiempo en marcadores de Profiler como Gfx.WaitForPresentOnGfxThread, y tu hilo de renderizado muestra simultáneamente marcadores como Gfx.PresentFrame o <GraphicsAPIName>.WaitForLastPresent.

La siguiente captura se tomó en un Samsung Galaxy S7, utilizando la API gráfica Vulkan. Aunque parte del tiempo empleado en Gfx.PresentFrame en este ejemplo podría estar relacionado con la espera de VSync, la duración extrema de este marcador de Profiler indica que la mayor parte de este tiempo se emplea en esperar a que la GPU termine de renderizar el fotograma anterior.

En este juego, determinados eventos del juego activaban el uso de un sombreador que triplicaba el número de llamadas de dibujo renderizadas por la GPU. Entre los problemas más comunes que deben investigarse al analizar el rendimiento de la GPU se incluyen los siguientes:

  • Efectos caros de postprocesamiento a pantalla completa, incluidos los culpables habituales, como la oclusión ambiental y la floración.
  • Sombreadores de fragmentos caros causados por:
  • Lógica de bifurcación
  • Utilización de la precisión flotante completa en lugar de la precisión media
  • Uso excesivo de registros que afectan a la ocupación del frente de onda de las GPU.
  • Sobredimensionamiento en la cola de renderizado Transparente causado por una interfaz de usuario, sistemas de partículas o efectos de postprocesado ineficaces.
  • Resoluciones de pantalla excesivamente altas, como las de las pantallas 4K o las pantallas Retina de los dispositivos móviles.
  • Micro triángulos causados por una geometría de malla densa o una falta de LOD, que es un problema particular en las GPU de móviles pero que también puede afectar a las GPU de PC y consolas.
  • Fallos de caché y pérdida de ancho de banda de memoria de la GPU causados por texturas sin comprimir o texturas de alta resolución sin mipmaps.
  • Sombreadores de geometría o teselación, que pueden ejecutarse varias veces por fotograma si se activan las sombras dinámicas.

Si tu aplicación parece estar vinculada a la GPU, puedes utilizar el depurador de fotogramas como una forma rápida de comprender los lotes de llamadas de dibujo que se envían a la GPU. Sin embargo, esta herramienta no puede presentar ninguna información específica de sincronización de la GPU, sólo cómo está construida la escena en general.

La mejor forma de investigar la causa de los cuellos de botella de la GPU es examinar una captura de GPU de un perfilador de GPU adecuado. La herramienta que se utilice dependerá del hardware de destino y de la API gráfica elegida.

clave de unidad art 21 11
¿Quieres más información?

Descárgate gratis el libro electrónico Ultimate guide to profiling Unitygames para obtener todos los consejos y las mejores prácticas.

¿Le ha resultado útil este contenido?