Cómo crear perfiles de tu juego de WebGL con la marca about:tracing

Lilli Thompson
Lilli Thompson

Si no puedes medirlo, no puedes mejorarlo.

Lord Kelvin

Para que tus juegos HTML5 se ejecuten más rápido, primero tienes que identificar los cuellos de botella en el rendimiento, pero esto puede ser difícil. La evaluación de los datos de fotogramas por segundo (FPS) es el primer paso, pero para ver el panorama completo, debes captar los matices de las actividades de Chrome.

La herramienta about:tracing proporciona información que te ayuda a evitar soluciones alternativas precipitadas dirigidas a mejorar el rendimiento, pero que son, en esencia, conjeturas bien intencionadas. Ahorrarás mucho tiempo y energía, obtendrás una idea más clara de lo que hace Chrome con cada fotograma y usarás esta información para optimizar tu juego.

Información sobre el seguimiento

La herramienta Acerca de registro de Chrome te ofrece una ventana de todas las actividades de Chrome durante un período con tanto nivel de detalle que puede resultarte abrumador al principio. Muchas de las funciones de Chrome están instrumentadas para realizar seguimientos de manera predeterminada, por lo que, sin tener que hacer ninguna instrumentación manual, puedes usar el seguimiento para realizar un seguimiento de tu rendimiento. (Consulta una sección posterior sobre cómo instrumentar manualmente tu JS).

Para acceder a la vista de seguimiento, simplemente escribe "about:tracing" en el cuadro multifunción (barra de direcciones) de Chrome.

Cuadro multifunción de Chrome
Escribe "about:tracing" en el cuadro multifunción de Chrome

Desde la herramienta de registro, puedes comenzar a grabar, ejecutar el juego durante unos segundos y, luego, ver los datos de registro. Este es un ejemplo de cómo podrían verse los datos:

Resultado del seguimiento simple
Resultado del seguimiento simple

Sí, es confuso. Hablemos sobre cómo leerla.

Cada fila representa un proceso para el que se genera un perfil, el eje de izquierda a derecha indica el tiempo y cada cuadro de color es una llamada a función instrumentada. Hay filas para varios tipos de recursos diferentes. Los más interesantes para la generación de perfiles de juegos son CrGpuMain, que muestra lo que hace la unidad de procesamiento de gráficos (GPU), y CrRendererMain. Cada seguimiento contiene líneas CrRendererMain para cada pestaña abierta durante el período de seguimiento (incluida la pestaña about:tracing en sí).

Cuando leas datos de seguimiento, tu primera tarea será determinar qué fila de CrRendererMain corresponde a tu juego.

Resultado de seguimiento simple destacado
Resultado del seguimiento simple destacado

En este ejemplo, los dos candidatos son 2216 y 6516. Lamentablemente, en este momento no existe una forma pulida de seleccionar tu aplicación, excepto buscar la línea que realiza muchas actualizaciones periódicas (o, si instrumentaste manualmente tu código con puntos de seguimiento para buscar la línea que contiene los datos de seguimiento). En este ejemplo, parece que 6516 está ejecutando un bucle principal de la frecuencia de las actualizaciones. Si cierras todas las demás pestañas antes de iniciar el seguimiento, será más fácil encontrar el CrRendererMain correcto. Sin embargo, es posible que todavía haya filas de CrRendererMain para procesos distintos del juego.

Buscando tu encuadre

Una vez que hayas encontrado la fila correcta en la herramienta de registro de tu juego, el siguiente paso es encontrar el bucle principal. El bucle principal parece un patrón repetitivo en los datos de seguimiento. Si deseas navegar por los datos de seguimiento, puedes usar las teclas W, A, S y D: A y D para moverte hacia la izquierda o la derecha (hacia la derecha y en el tiempo), y W y S para acercar y alejar los datos. El bucle principal es un patrón que se repite cada 16 milisegundos si el juego se ejecuta a 60 Hz.

Parece que hay tres fotogramas de ejecución
Parece tres marcos de ejecución

Una vez que hayas localizado la señal de monitoreo de funcionamiento de tu juego, podrás conocer en detalle lo que hace exactamente tu código en cada fotograma. Usa W, A, S y D para acercar la imagen hasta que puedas leer el texto en los cuadros de funciones.

En profundidad un marco de ejecución
Analiza en profundidad un marco de ejecución

Esta colección de cuadros muestra una serie de llamadas a funciones, y cada llamada se representa con un cuadro de color. Se llamó a cada función desde el cuadro superior, por lo que en este caso, puedes ver que MessageLoop::RunTask se llama RenderWidget::OnSwapBuffersComplete, que, a su vez, se llama RenderWidget::DoDeferredUpdate, y así sucesivamente. Al leer estos datos, puedes obtener una vista completa de lo que se llamó qué y cuánto tiempo tardó cada ejecución.

Pero aquí es donde se vuelve un poco pegajoso. La información que se expone a través de about:tracing corresponde a las llamadas a funciones sin procesar provenientes del código fuente de Chrome. Puedes hacer conjeturas fundamentadas sobre lo que hace cada función a partir del nombre, pero la información no es exactamente fácil de usar. Es útil ver el flujo general de tu fotograma, pero necesitas algo más legible para comprender qué sucede.

Agrega etiquetas de seguimiento

Afortunadamente, existe una forma sencilla de agregar instrumentación manual a tu código para crear datos de seguimiento: console.time y console.timeEnd.

console.time("update");
update();
console.timeEnd("update");
console.time("render");
update();
console.timeEnd("render");

El código anterior crea nuevos cuadros en el nombre de la vista de seguimiento con las etiquetas especificadas, por lo que, si vuelves a ejecutar la app, verás cuadros de "actualización" y "renderización", que muestran el tiempo transcurrido entre las llamadas de inicio y finalización de cada etiqueta.

Etiquetas agregadas manualmente
Etiquetas agregadas manualmente

De esta manera, puedes crear datos de seguimiento legibles por humanos para realizar el seguimiento de hotspots en tu código.

¿GPU o CPU?

Con los gráficos acelerados por hardware, una de las preguntas más importantes que puedes hacer durante la generación de perfiles es: ¿este código está vinculado a la GPU o a la CPU? Con cada fotograma, realizarás un trabajo de renderización en la GPU y algo de lógica en la CPU. Para comprender por qué tu juego es lento, necesitarás ver cómo se equilibra el trabajo en los dos recursos.

Primero, busca la línea en la vista de seguimiento llamada CrGPUMain, que indica si la GPU está ocupada en un momento determinado.

Seguimientos de GPU y CPU

Puedes ver que cada fotograma de tu juego genera trabajo de la CPU en CrRendererMain y en la GPU. En el seguimiento anterior, se muestra un caso de uso muy simple en el que tanto la CPU como la GPU están inactivas durante la mayor parte de cada fotograma de 16 ms.

La vista de seguimiento es muy útil cuando tienes un juego que se ejecuta lentamente y no sabes con certeza qué recurso estás agotando. Observar la relación entre la GPU y la CPU es fundamental para la depuración. Toma el mismo ejemplo que antes, pero agrega un poco de trabajo adicional en el bucle de actualización.

console.time("update");
doExtraWork();
update(Math.min(50, now - time));
console.timeEnd("update");

console.time("render");
render();
console.timeEnd("render");

Ahora, verás un seguimiento similar al siguiente:

Seguimientos de GPU y CPU

¿Qué nos dice este seguimiento? Podemos ver que el fotograma que se muestra va de 2,270 ms a 2,320 ms, lo que significa que cada fotograma toma alrededor de 50 ms (una velocidad de fotogramas de 20 Hz). Puedes ver porciones de cuadros de color que representan la función de renderización junto al cuadro de actualización, pero el marco está totalmente dominado por la actualización.

A diferencia de lo que sucede en la CPU, puedes ver que la GPU sigue inactiva durante la mayor parte de cada fotograma. Para optimizar este código, puedes buscar operaciones que se puedan realizar en el código del sombreador y moverlas a la GPU para aprovechar al máximo los recursos.

¿Qué ocurre cuando el código del sombreador es lento y la GPU está sobrecargada? ¿Qué sucede si quitamos el trabajo innecesario de la CPU y, en su lugar, agregamos algo de trabajo en el código del sombreador de fragmentos? Este es un sombreador de fragmentos que es innecesariamente costoso:

#ifdef GL_ES
precision highp float;
#endif
void main(void) {
  for(int i=0; i<9999; i++) {
    gl_FragColor = vec4(1.0, 0, 0, 1.0);
  }
}

¿Cómo se ve un seguimiento de código que usa ese sombreador?

Seguimientos de GPU y CPU cuando se usa un código GPU lento
Registros de GPU y CPU cuando se usa un código GPU lento

Nuevamente, observa la duración de un fotograma. Aquí, el patrón repetitivo va de 2,750 ms a 2,950 ms, con una duración de 200 ms (velocidad de fotogramas de 5 Hz aproximadamente). La línea CrRendererMain está casi completamente vacía, lo que significa que la CPU está inactiva la mayor parte del tiempo, mientras que la GPU está sobrecargada. Esta es una señal segura de que los sombreadores están demasiado pesados.

Si no pudiste ver exactamente lo que causa la baja velocidad de fotogramas, podrías observar la actualización de 5 Hz y verte tentado a ingresar al código del juego y comenzar a intentar optimizar o quitar la lógica del juego. En este caso, eso no serviría en absoluto, porque la lógica en el bucle de juego no es lo que está consumiendo tiempo. De hecho, lo que indica este seguimiento es que realizar más trabajo de la CPU en cada fotograma sería esencialmente "libre" porque la CPU queda inactiva, por lo que darle más trabajo no afectará el tiempo que tardará el fotograma.

Ejemplos reales

Ahora veamos cómo son los datos de seguimiento de un juego real. Una de las mejores características de los juegos creados con tecnologías web abiertas es que puedes ver qué sucede en tus productos favoritos. Si deseas probar las herramientas de generación de perfiles, puedes elegir tu título favorito de WebGL en Chrome Web Store y generar un perfil con about:tracing. Este es un seguimiento de ejemplo tomado del excelente juego Skid Racer.

Cómo rastrear un juego real
Cómo rastrear un juego real

Parece que cada fotograma tarda unos 20 ms, lo que significa que la velocidad de fotogramas es de 50 FPS. Puedes ver que el trabajo está equilibrado entre la CPU y la GPU, pero la GPU es el recurso que más demanda. Si deseas ver cómo es generar un perfil de ejemplos reales de juegos con WebGL, prueba algunos de los títulos de Chrome Web Store creados con WebGL, incluidos los siguientes:

Conclusión

Si quieres que tu juego se ejecute a 60 Hz, entonces, para cada fotograma, todas tus operaciones deben ajustarse a 16 ms de CPU y 16 ms de tiempo de GPU. Cuenta con dos recursos que se pueden usar en paralelo y puede cambiar el trabajo entre ellos para maximizar el rendimiento. La vista de seguimiento "Acerca de:seguimiento" de Chrome es una herramienta invaluable para obtener estadísticas sobre lo que hace realmente tu código y te ayudará a maximizar el tiempo de desarrollo al abordar los problemas correctos.

¿Qué sigue?

Además de la GPU, también puedes hacer un seguimiento de otras partes del tiempo de ejecución de Chrome. Chrome Canary, la primera versión de Chrome, está instrumentado para hacer un seguimiento de IO, IndexedDB y muchas otras actividades. Recomendamos que leas este artículo de Chromium para comprender mejor el estado actual de los eventos de seguimiento.

Si eres un desarrollador de juegos web, asegúrate de mirar el siguiente video. Se trata de una presentación del equipo de Developer Advocate de Google en la GDC 2012 sobre la optimización del rendimiento para los juegos de Chrome.