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

Lilli Thompson
Lilli Thompson

Si no puedes medirlo, no podrás mejorarlo.

Lord Kelvin

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

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

Hola sobre:seguimiento

La herramienta acerca de seguimiento de Chrome te ofrece una ventana a todas las actividades de Chrome durante un período con un nivel de detalle tan alto que te puede resultar abrumador al principio. Muchas de las funciones de Chrome están instrumentadas para realizar el seguimiento desde el primer momento, por lo que, sin realizar ninguna instrumentación manual, puedes usar about:tracing para hacer un seguimiento de tu rendimiento. (Consulta una sección posterior sobre la instrumentación manual de tu JS).

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

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

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

Resultado de seguimiento simple
Resultado de seguimiento simple

Sí, es confuso. Veamos cómo leerlo.

Cada fila representa un proceso para el que se crea un perfil, el eje de izquierda a derecha indica el tiempo y cada cuadro de color es una llamada a función instrumentada. Existen filas para distintos tipos de recursos. 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 propia pestaña about:tracing).

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

Resultado de seguimiento simple destacado
Resultado de seguimiento simple destacado

En este ejemplo, los dos candidatos son: 2216 y 6516. Lamentablemente, en la actualidad, no hay una manera 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, buscar la línea que contiene tus datos de seguimiento). En este ejemplo, parece que 6516 ejecuta un bucle principal a partir 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 aún haya filas CrRendererMain para procesos que no sean tu juego.

Cómo encontrar tu marco

Una vez que hayas localizado la fila correcta en la herramienta de registro del juego, el siguiente paso es encontrar el bucle principal. El bucle principal es similar a un patrón que se repite en los datos de seguimiento. Puedes navegar por los datos de seguimiento con las teclas W, A, S y D: A y D para moverte hacia la izquierda o derecha (adelante y hacia atrás en el tiempo), y W y S para acercar y alejar los datos. Es esperable que el bucle principal sea un patrón que se repite cada 16 milisegundos si el juego se ejecuta a 60 Hz.

Parecen tres marcos de ejecución
Se parecen a tres marcos de ejecución

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

En lo profundo de un marco de ejecución
Profundo en un marco de ejecución

Este conjunto de cuadros muestra una serie de llamadas a funciones, en las que cada llamada se representa con un cuadro de color. Cada función se llamó a cada función con el cuadro que está arriba, por lo que, en este caso, puedes ver la MessageLoop::RunTask llamado RenderWidget::OnSwapBuffersComplete, que, a su vez, se llamaba RenderWidget::DoDeferredUpdate, etc. Al leer estos datos, puedes obtener una vista completa de lo que llamó cada ejecución y cuánto tiempo tardó.

Pero aquí es donde se vuelve un poco pegajoso. La información que expone about:tracing contiene las llamadas a funciones sin procesar 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 marco, pero necesitas algo un poco más legible para que en realidad sepa qué está pasando.

Agrega etiquetas de seguimiento

Por suerte, 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 "update" y "render" que muestran el tiempo transcurrido entre las llamadas de inicio y finalización de cada etiqueta.

Etiquetas agregadas manualmente
Etiquetas agregadas manualmente

Con esto, puedes crear datos de seguimiento legibles por humanos para hacer un seguimiento de los 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 la siguiente: ¿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 entender qué le hace lento al juego, necesitas ver cómo se equilibra el trabajo entre 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 CPU en CrRendererMain y en la GPU. El seguimiento anterior muestra un caso de uso muy simple en el que la CPU y la GPU están inactivas durante la mayor parte de cada fotograma de 16 ms.

La vista de seguimiento resulta muy útil cuando tienes un juego que se ejecuta lentamente y no sabes con certeza qué recurso se está agotando. La clave de la depuración es observar cómo se relacionan la GPU y la CPU. 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 tarda alrededor de 50 ms (una velocidad de fotogramas de 20 Hz). Puedes ver franjas de cuadros de colores que representan la función de renderización junto al cuadro de actualización, pero el marco está completamente 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 al código del sombreador de fragmentos? Este es un sombreador de fragmentos 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 código de GPU lento
Seguimientos de GPU y CPU cuando se usa código de GPU lento

Nuevamente, observa la duración de un fotograma. Aquí, el patrón repetitivo va de aproximadamente 2,750 ms a 2,950 ms, una duración de 200 ms (velocidad de fotogramas de aproximadamente 5 Hz). La línea de 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 clara de que tus sombreadores son demasiado pesados.

Si no podías ver exactamente qué causaba la baja velocidad de fotogramas, podrías observar la actualización de 5 Hz y sentir la tentación de ir al código del juego y comenzar a intentar optimizar o quitar la lógica del juego. En este caso, eso no sería para nada bueno, porque la lógica del bucle de juego no es lo que está consumiendo tiempo. De hecho, este seguimiento indica que hacer más trabajo en la CPU en cada fotograma sería "gratuito". en el que la CPU permanece inactiva, por lo que darle más trabajo no afectará el tiempo que tarde el fotograma.

Ejemplos reales

Ahora veamos cómo se ven los datos de seguimiento de un juego real. Uno de los aspectos geniales de los juegos creados con tecnologías web abierta es que puedes ver lo que ocurre en tus productos favoritos. Si quieres 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 ejemplo de seguimiento tomado del excelente juego Skid Racer de WebGL.

Trayectoria de un juego real
Cómo hacer un seguimiento de un juego real

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

Conclusión

Si quieres que tu juego se ejecute a 60 Hz, todas tus operaciones deben ajustarse a 16 ms de CPU y 16 ms de tiempo de GPU por cada fotograma. Tienes dos recursos que se pueden usar en paralelo y puedes cambiar el trabajo entre ellos para maximizar el rendimiento. La vista about:tracing de Chrome es una herramienta invaluable para conocer lo que hace realmente tu código y te ayudará a maximizar el tiempo de desarrollo abordando los problemas adecuados.

Próximos pasos

Además de la GPU, también puedes realizar el seguimiento de otras partes del tiempo de ejecución de Chrome. Chrome Canary, la versión preliminar de Chrome, está instrumentado para hacer un seguimiento de E/S, IndexedDB y muchas otras actividades. Para entender mejor el estado actual de los eventos de seguimiento, consulta este artículo de Chromium.

Si eres un desarrollador de juegos web, asegúrate de mirar el siguiente video. Es una presentación del equipo de representantes de desarrolladores de juegos de Google en la GDC 2012 sobre la optimización del rendimiento de los juegos de Chrome: