Si no puedes medirlo, no puedes 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. Evaluar los datos de fotogramas por segundo (FPS) es un buen comienzo, pero para ver el panorama completo, debes comprender los matices de las actividades de Chrome.
La herramienta about:tracing
proporciona las estadísticas que te ayudan a evitar soluciones apresuradas destinadas a mejorar el rendimiento, pero que, en esencia, son conjeturas bien intencionadas. Ahorrarás mucho tiempo y energía, obtendrás una imagen más clara de lo que hace Chrome con cada fotograma y podrás usar esta información para optimizar tu juego.
Hola about:tracing
La herramienta about:tracing
de Chrome te brinda una vista de todas las actividades de Chrome durante un período con tanto detalle que al principio podría parecerte abrumador. Muchas de las funciones de Chrome están instrumentadas para el seguimiento de forma predeterminada, 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 cómo instrumentar manualmente tu código JS)
Para ver la vista de seguimiento, simplemente escribe "about:tracing" en la barra omni (barra de direcciones) de Chrome.
Desde la herramienta de seguimiento, puedes comenzar a grabar, 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:
Sí, es bastante confuso. Hablemos de cómo leerlo.
Cada fila representa un proceso al que se le está generando 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. Los más interesantes para generar perfiles de juegos son CrGpuMain, que muestra lo que hace la unidad de procesamiento gráfico (GPU), y CrRendererMain. Cada registro contiene líneas de CrRendererMain para cada pestaña abierta durante el período de registro (incluida la pestaña about:tracing
).
Cuando lees los datos de seguimiento, tu primera tarea es determinar qué fila de CrRendererMain corresponde a tu juego.
En este ejemplo, los dos candidatos son 2216 y 6516. Lamentablemente, por el momento, no hay una forma pulida de elegir 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 haya filas de CrRendererMain para procesos que no sean de tu juego.
Cómo encontrar tu marco
Una vez que hayas encontrado la fila correcta en la herramienta de seguimiento de tu juego, el siguiente paso es encontrar el bucle principal. El bucle principal se ve como un patrón repetitivo 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 la derecha (hacia atrás y hacia adelante en el tiempo) y W y S para acercar y alejar los datos. Se espera que el bucle principal sea un patrón que se repita cada 16 milisegundos si el juego se ejecuta a 60 Hz.
Una vez que hayas localizado el indicador de estado de tu juego, podrás analizar qué 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 función.
Esta colección de cuadros muestra una serie de llamadas a función, cada una representada por un cuadro de color. El cuadro que está encima de cada función la llamó, por lo que, en este caso, puedes ver que MessageLoop::RunTask llamó a RenderWidget::OnSwapBuffersComplete, que, a su vez, llamó a RenderWidget::DoDeferredUpdate, y así sucesivamente. Cuando lees estos datos, puedes obtener una vista completa de qué llamó a qué y cuánto tiempo tardó cada ejecución.
Pero aquí es donde se complica un poco. La información que expone about:tracing
son las llamadas a función 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 del todo fácil de usar. Es útil ver el flujo general de tu fotograma, pero necesitas algo más legible para comprender lo que sucede.
Cómo agregar etiquetas de seguimiento
Afortunadamente, hay 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 cuadros nuevos 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.
Con esto, puedes crear datos de seguimiento legibles por humanos para hacer un seguimiento de los puntos calientes 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 una lógica en la CPU. Para comprender qué hace que el juego sea lento, deberás 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.
Puedes ver que cada fotograma del 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 es muy útil cuando tienes un juego que se ejecuta con lentitud y no sabes qué recurso estás agotando. La clave para la depuración es observar cómo se relacionan las líneas de 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 registro que se ve de la siguiente manera:
¿Qué nos dice este registro? Podemos ver que el fotograma en la imagen va de alrededor de 2270 ms a 2320 ms, lo que significa que cada fotograma tarda alrededor de 50 ms (una velocidad de fotogramas de 20 Hz). Puedes ver fragmentos 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 mejor los recursos.
¿Qué sucede 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 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?
Una vez más, ten en cuenta la duración de un fotograma. Aquí, el patrón repetitivo va de alrededor de 2750 ms a 2950 ms, con una duración de 200 ms (tasa de fotogramas de alrededor de 5 Hz). 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. Esto es un indicador seguro de que tus sombreadores son demasiado pesados.
Si no tuvieras visibilidad sobre qué causaba exactamente la baja velocidad de fotogramas, podrías observar la actualización de 5 Hz y sentir la tentación de ingresar al código del juego y comenzar a intentar optimizar o quitar la lógica del juego. En este caso, eso no sería de ninguna ayuda, ya que la lógica del bucle de juego no es lo que consume tiempo. De hecho, lo que indica este seguimiento es que hacer más trabajo de CPU en cada fotograma sería, en esencia, “gratuito”, ya que la CPU está inactiva, por lo que darle más trabajo no afectará el tiempo que tarda el fotograma.
Ejemplos reales
Ahora veamos cómo se ven los datos de seguimiento de un juego real. Una de las ventajas de los juegos creados con tecnologías web abiertas es que puedes ver qué sucede en tus productos favoritos. Si quieres probar las herramientas de generación de perfiles, puedes elegir tu título de WebGL favorito de Chrome Web Store y generarle un perfil con about:tracing
. Este es un ejemplo de seguimiento tomado del excelente juego de WebGL Skid Racer.
Parece que cada fotograma tarda alrededor de 20 ms, lo que significa que la velocidad de fotogramas es de alrededor de 50 FPS. Puedes ver que el trabajo está equilibrado entre la CPU y la GPU, pero la GPU es el recurso más demandado. Si quieres ver cómo es generar perfiles de ejemplos reales de juegos de WebGL, prueba algunos de los títulos de Chrome Web Store creados con WebGL, como los siguientes:
Conclusión
Si quieres que tu juego se ejecute a 60 Hz, para cada fotograma, todas tus operaciones deben caber en 16 ms de CPU y 16 ms de GPU. 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 obtener estadísticas sobre lo que realmente hace tu código y te ayudará a maximizar tu tiempo de desarrollo abordando los problemas correctos.
Próximos pasos
Además de la GPU, también puedes hacer un seguimiento de otras partes del entorno de ejecución de Chrome. Chrome Canary, la versión preliminar de Chrome, está instrumentada para registrar la E/S, IndexedDB y varias otras actividades. Lee este artículo de Chromium para comprender mejor el estado actual de los eventos de seguimiento.
Si eres desarrollador de juegos web, asegúrate de mirar el siguiente video. Es una presentación del equipo de defensores de desarrolladores de juegos de Google en GDC 2012 sobre la optimización del rendimiento de los juegos de Chrome: