Mejora de rendimiento: La invalidación global en VALORANT

Esta es la historia del equipo de rendimiento en su misión por mejorar el cliente.

¡Hola! Soy Aaron Cheney, ingeniero de software del equipo de rendimiento de VALORANT. El rendimiento es un elemento clave a la hora de proteger la integridad competitiva de nuestro juego, y el equipo se responsabiliza de monitorizar, mantener y mejorar el rendimiento tanto del servidor como del cliente.

Nos hace mucha ilusión poder compartir noticias sobre una característica que lleva varios meses en desarrollo: la invalidación global. En seguida os explicaremos los detalles, pero, antes de nada, vamos a echar un vistazo al impacto que ha tenido esta característica en el rendimiento del cliente desde la versión 4.03.

Os adelantamos que ha sido bastante increíble.

AskVal_March22_Global_Invalidation_Graph_3.jpg

La invalidación global ha traído mejorías para gran parte de nuestros jugadores. De hecho, es el mayor aumento de rendimiento del cliente desde el lanzamiento.

Las métricas prometen mucho (y estamos contentísimos con los resultados), pero es importante entender qué significa todo esto. Trabajamos con grandes cantidades de datos complejos; organizarlos, filtrarlos y manipularlos nos ayuda a entender mejor la experiencia de los jugadores. Para haceros una idea general, hay que tener todo esto en mente:

  • El cuadro compara métricas "por versión" y "FPS medios"; cuanto más altas sean las cifras, mejor.
  • Cada línea representa una configuración de hardware frecuente entre nuestros jugadores (es decir, la combinación de una tarjeta gráfica concreta con una CPU determinada). Al analizar los datos, estas configuraciones son el elemento que más acertadamente predice el rendimiento del equipo. Los equipos que tengan el mismo procesador y tarjeta gráfica se agrupan en estos cuadros.
  • Obtenemos todos los datos de dos fuentes distintas: las partidas normales y las competitivas. Como estos son los modos de juego más populares, invertimos gran parte de nuestro esfuerzo en averiguar cómo mejorar el rendimiento en áreas relacionadas con ellos.
  • Hemos excluido las partidas que no tienen 10 jugadores. Así, los casos más extremos no desplazarán los datos (el rendimiento es más alto cuantos menos jugadores haya en la partida).

RESUMEN DE LA INVALIDACIÓN GLOBAL

global-invalidation-summary-flow.jpg

La invalidación global supone una mejora de hasta un 15 % para clientes vinculados al procesador (equipos de gama media y alta, por lo general). Conseguir ese porcentaje ha supuesto el esfuerzo y la colaboración de varios equipos durante muchos meses. El proceso que desarrollamos para identificar áreas del juego en las que podíamos optimizar ha dado sus frutos, y gestionar los riesgos del proceso nos ha permitido proporcionar una experiencia más estable a los jugadores.

¿A quién beneficia esto?: Ajustando las expectativas

Según los datos del servidor normal, la invalidación global ha supuesto una mejora de hasta un 15 % para configuraciones del cliente vinculadas al procesador (equipos de gama media y alta, por lo general).

Aunque la tendencia a mejorar resulta evidente en la suma de todos los datos, no representa la experiencia inmediata de juego. Tampoco garantiza que todos los equipos con el mismo hardware vayan a obtener los mismos resultados.

Eso quiere decir que el rendimiento básico de VALORANT en equipos vinculados al procesador ha aumentado en términos generales, pero los resultados concretos de vuestro ordenador dependen de una serie de factores individuales.

ENTENDER LA INVALIDACIÓN GLOBAL

Antes de resumiros en qué consiste la invalidación global, tenemos que explicaros unas cuantas cosas sobre elementos de la IU en Unreal Engine.

Widgets y estructuras de árbol

Los elementos de la IU (popularmente denominados widgets por su nombre en inglés) están compuestos de piezas más pequeñas que tienen estructura de árbol. Esta estructura se parece bastante a la del sistema de archivos y carpetas de vuestro ordenador. Un widget puede tener cualquier cantidad de "hijos" (al igual que una carpeta puede contener cualquier cantidad de archivos).

Estas piezas se van combinando para crear widgets complejos. El contador de munición, por ejemplo, está integrado por varias piezas, y el árbol correspondiente es algo más o menos así:

Screenshot_2022-03-07_at_16-30-57_PRF_-_Global_Invalidation_article.png

Si se junta todo, el contador de munición acaba con esta pinta:

AskVal_March22_Valorant_UI_Elements.jpg

(Los recuadros rojos están enfatizados para que resulte más claro; en la práctica, muchos bordes se solaparían).

Cuando se cambia un widget (o varios) del árbol, es posible que afecte a otros. Por ejemplo, si uno se desplaza a otro punto de la pantalla, todos los widgets que estén debajo también tendrán que recalcular su posición. Estos cambios los gestiona un sistema que llamamos "invalidación", que es sobre lo que vamos a hablar en la siguiente sección.

La invalidación

La invalidación es el mecanismo que Unreal Engine emplea para indicar cuándo un widget concreto ha cambiado y necesita actualizarse.

Hay varios motivos por los que hay que invalidar los widgets: la animación, el color, la opacidad, el tamaño, el orden, el texto, las imágenes y muchas otras propiedades pueden ir cambiando en respuesta a cosas que suceden en la partida. Cuando se dan estos cambios, el widget queda invalidado, lo que indica al sistema que debe actualizarlo.

Para complicar este proceso todavía más, hay varios tipos de invalidación. Estos son algunos ejemplos:

  • Distribución: cuando lo que varía es el tamaño del widget (consume muchos recursos).
  • Aspecto: cuando el aspecto del widget cambia, pero el tamaño permanece igual.
  • Orden de hijos: cuando cambia el orden de los widgets dentro del árbol (implica distribución, así que también consume muchos recursos).
  • Visibilidad: cuando el widget pasa de ser visible a invisible y viceversa (también implica distribución, así que consume muchos recursos).

Estos tipos de invalidación se utilizan para indicar qué operaciones son necesarias para aplicar el cambio pertinente.

Cuando un widget depende de otro, la cosa se complica aún más. Los widgets se organizan en jerarquías, y su distribución depende de una serie de factores. Invalidar un único widget puede requerir invalidar otros tantos para aplicar correctamente los cambios. Por ejemplo, si hay varios widgets organizados en una distribución vertical (como sucede con la lista de amigos en el panel social) y el orden cambia (por ejemplo, cuando se conecta un amigo), todos los widgets de la distribución tienen que actualizarse.

Este sistema tiene varios objetivos:

  • Invalidar la menor cantidad de widgets posible. Esto reduce la cantidad que tenemos que actualizar para aplicar los cambios correctamente.
  • Solo invalidar widgets cuando resulte necesario. Invalidar un widget cuando no hace falta supone una pérdida de ciclos de CPU que no podemos permitirnos.
  • Cuando no se invalida un widget, almacenar el resultado para cargarlo rápidamente en cada fotograma. Si nada cambia, ahorramos ciclos de CPU.

Y con esto ya sabéis suficiente sobre el tema para entender cómo se actualizan los widgets y qué tipos de cosas dan lugar a invalidaciones. Ahora vamos a ver cómo han aplicado los desarrolladores toda esta teoría.

Cuadros de invalidación

Unreal Engine cuenta con un componente, los cuadros de invalidación, que permite agrupar varios widgets. Si están en un mismo cuadro de invalidación, los widgets no podrán precargarse, marcarse o trazarse. En su lugar, el resultado se almacena en un búfer de vértice.

Cuando se invalida uno de los widgets del cuadro, los datos almacenados se descartan y el widget se actualiza y se traza de nuevo. Es cierto que refrescar la memoria para un único fotograma consume bastantes recursos, pero la amortización resultante compensa a la larga.

Los cuadros de invalidación son un elemento clave del rendimiento de la IU de VALORANT, y lo fueron especialmente en los preparativos para el lanzamiento. Sin embargo, los cuadros también tienen su coste:

  • Es necesario que los desarrolladores entiendan qué widgets funcionarán bien si se agrupan juntos en un cuadro de invalidación. Si un widget necesita actualizarse con regularidad, no es buen candidato.
  • Colocar widgets en un cuadro de invalidación es algo que los desarrolladores deben hacer manualmente. Hacerlo con todos los widgets del juego no es factible, así que el equipo también debe saber priorizar los widgets que merezca la pena agrupar en un cuadro.

Si queréis saber más sobre los cuadros de invalidación, echadle un vistazo a la documentación de Epic.

¡Ahora ya tenemos suficiente contexto para hablar sobre la invalidación global!

Llega la invalidación global

A estas alturas, seguro que estáis pensando: "¿Y por qué no ponéis todos los elementos de la IU en un cuadro de invalidación global?". Pues eso es precisamente para lo que sirve la invalidación global (más o menos).

La invalidación global pretende mejorar el rendimiento de la IU en todo el juego a la vez que reduce la cantidad de trabajo manual necesario para colocar los widgets en cuadros de invalidación individuales. Combina lo mejor de ambos mundos.

Sin embargo, al menos en la versión UE4.25 (la versión de Unreal Engine que usamos para VALORANT), la invalidación global no es compatible con todo tipo de widgets. Las versiones posteriores han implementado mejoras, pero VALORANT no puede incorporarlas inmediatamente. Además, no sabíamos muy bien cuánto aumentaría realmente el rendimiento y la velocidad de VALORANT con la invalidación global.

Aquí es donde comienza nuestra labor.

¿POR QUÉ DECIDIMOS EMBARCARNOS EN ESTE PROYECTO?

A finales de julio de 2021, el equipo decidió realizar pruebas internas del sistema de invalidación global. Hicimos unos cuantos cambios menores para arreglar un par de errores y completar la prueba con éxito. Sin embargo, sabíamos que iríamos encontrando más y más errores durante el proceso... Y vaya que si lo hicimos.

Cuando acabamos, teníamos más de 20 bugs, y eran solo los más evidentes. Teníamos claro que probablemente se nos hubieran colado otros más discretos y problemáticos, y también nos habían quedado un par de situaciones poco frecuentes por testear.

Pero... ¿qué hay de los resultados de la invalidación global? Pues fueron maravillosos.

Tras analizar los datos de esa primera prueba, concluimos que la IU funcionaba cerca de un 35 % más rápido. (Nota: la IU solo es una parte del coste de cada fotograma).

Pero todavía nos quedaban un montón de preguntas:

  • ¿Cuánto tiempo necesitábamos para arreglar todos los errores?
  • ¿Qué equipos debían ser los responsables del trabajo?
  • ¿Deberíamos priorizar este proyecto frente a otros que tuviéramos planeados? Para poder cumplir con un ritmo de lanzamientos regular, solemos fijar nuestros calendarios con meses de antelación. El trabajo que surge sobre la marcha, por muy emocionante que nos resulte (como esta vez), resulta difícil de encajar.
  • ¿Y si solucionar los errores reduce la mejora de rendimiento? Corregir todos los errores detectados requeriría muchos cambios en el código, y cada uno tenía el potencial de aumentar el consumo de la IU.
  • ¿Cuándo deberíamos abordar este proyecto? Como sabíamos que la invalidación global estaba incorporándose activamente a las versiones más recientes de Unreal Engine, teníamos que plantearnos cuándo queríamos empezar a integrar los cambios de Epic.

Al final, decidimos que el proyecto merecía la pena por varios motivos.

Integraciones de Unreal Engine y consideraciones de calendario

Aunque las versiones 4.26 y 4.27 de Unreal Engine han realizado avances significativos en términos de invalidación global, el calendario de integración de VALORANT va con algo de retraso. No incorporamos inmediatamente los cambios más recientes para evitar riesgos y garantizar estabilidad a nuestros jugadores.

Nuestro calendario indicaba que íbamos a seguir usando la versión 4.25 de Unreal Engine durante muchos meses más, y eso significaba que los jugadores tardarían bastante más de un año en ver estas mejoras de rendimiento. No nos hacía mucha gracia la idea, la verdad.

Si queréis más detalles sobre cómo nos planteamos el proceso de incorporar mejoras de Unreal Engine a VALORANT, echad un vistazo a este hilo de Twitter del jefe de tecnología de VALORANT, Marcus Reid.

Mejoras de rendimiento registradas

La invalidación global representaba algo único en términos de ajustes de rendimiento: valores medidos. Durante la prueba interna, pudimos medir la mejora potencial de rendimiento y el camino hasta obtener esos valores fue bastante intuitivo.

Los ajustes de rendimiento son algo complicado. Hay que manejarse en centímetros, no kilómetros; los cambios graduales ayudan a mejorar el rendimiento a largo plazo, y no solemos encontrarnos cambios concretos que puedan ofrecernos mejoras de dos dígitos. Esta optimización nos resultaba demasiado tentadora como para ignorarla.

Incluso teniendo en cuenta la posibilidad de que perdiéramos parte de los resultados por las correcciones de errores, la invalidación global era la mejor oportunidad que teníamos de ofrecer una mejora significativa en un plazo razonable a nuestros jugadores.

¿CÓMO LO HEMOS CONSEGUIDO?

A pesar de que los experimentos iniciales comenzaron a finales de julio de 2021, no empezamos a concentrarnos del todo en estabilizar la nueva característica hasta finales de septiembre.

Integración selectiva de los cambios de Epic

Integrar por completo la 4.26 y la 4.27 no era factible, pero sabíamos que Epic había estado trabajando activamente en la invalidación global, así que decidimos indagar entre los miles de cambios para identificar cuáles podían ayudarnos a implementar la invalidación global sin perder estabilidad.

Integrar solo una parte de los cambios no fue tarea sencilla. Era crucial que modificáramos la cantidad mínima posible de características clave del motor, porque así mantendríamos la estabilidad a la par que incorporábamos los cambios de Epic que nos interesaban. Para evitar que esto afectara a otros desarrolladores, todo el trabajo lo realizamos en una rama separada del equipo principal de VALORANT.

Tras integrar una selección de los cambios de Epic a nuestro motor, nos pasamos varias semanas más arreglando todos los errores que pudimos mientras nos preparábamos para introducir la invalidación global a la rama principal del juego. Durante el proceso, creamos un interruptor que nos permitía deshabilitar rápidamente la característica si algo salía muy mal.

Después de corregir un montón de errores e incorporar muchos cambios de las versiones 4.26 y 4.27 de Unreal Engine, incorporamos la rama de trabajo aislada a la principal.

Denominadores comunes entre errores

Aunque muchos de los errores de la invalidación global se manifestaron de formas diferentes, muchas veces la causa era la misma. Esos errores fueron los que priorizamos arreglar, porque solucionábamos varios problemas con un solo cambio. Por ejemplo, hubo un cambio que arregló más de 10 bugs en distintos aspectos del juego. Analizar detalladamente la raíz del problema nos llevó a desarrollar soluciones contundentes que iban mejorando la estabilidad de la invalidación global.

Identificar y arreglar errores, hacer pruebas y repetir

Los meses siguientes consistieron en ir activando la invalidación global para una prueba, identificar una serie de errores, desactivarla de nuevo y solucionar estos errores, una y otra vez.

developer-bug-fix-flow.jpg


En cada ciclo encontrábamos menos errores. Seguimos haciendo esto hasta que el flujo constante de bugs se convirtió más bien en un goteo ocasional que acabó por detenerse del todo.

A finales de noviembre de 2021, todos los problemas graves estaban resueltos y la invalidación global funcionaba con relativa estabilidad.

Errores destacados
  • Astra provoca cuelgues en el juego: hubo un momento en el que todos los jugadores de Astra experimentaban cuelgues al cargar la partida. Arreglamos este error tras integrar los cambios de Epic.
  • Cuelgue por herencia múltiple: en C++, la herencia múltiple es un asunto complicado. Sin meternos mucho en el meollo, digamos que el orden de los destructores de una clase determinada no se ejecutó correctamente, lo que provocó un cuelgue. Lo solucionamos implementando un par de líneas de código que cambiaban el orden de la herencia. Si queréis saber más sobre la herencia múltiple, echadle un vistazo a esta página.
  • Sonido del chat infinito: en los menús, la barra del chat reproduce un sonido al pasar el ratón por encima. Para desgracia de todos nosotros, dimos con un error que hacía que este sonido se reprodujera varias veces por segundo. Para arreglarlo, tuvimos que entender cómo reaccionan los widgets a las acciones del ratón varias veces por fotograma.

    Pruebas de metodología

    Una característica de la invalidación global que nos hizo avanzar con pies de plomo (y hacer pruebas muy detalladas) es que afecta a todos los aspectos del juego. Literalmente.

    ¿La lista de amigos? Síp. ¿El botón para entrar en cola? También. ¿El menú de ajustes? Vaya que sí. ¿Mi puntería? Bueno, eso...

    La cosa es que hay elementos de la IU en todas las partes del juego, y muchas veces contienen información de importancia crítica. Cargarnos uno de esos elementos era una opción inaceptable.

    Para evitarlo, nuestro departamento de control de calidad elaboró un calendario de pruebas con varias estrategias. Así, fuimos confirmando con mayor certeza que la invalidación funcionaba como queríamos.

    Pruebas verticales

    Una "trayectoria vertical" en VALORANT representa la ruta principal que los jugadores suelen tomar desde que abren el cliente hasta que interactúan con la pantalla de fin de partida, pasando por ponerse en cola y jugar la partida en sí. Al centrarse en los elementos críticos del juego, el equipo de calidad pudo probar los elementos que más se utilizaban de todo el juego e identificar problemas rápidamente.

    Pruebas destructivas

    Las pruebas destructivas continúan la labor de las verticales. Este tipo de pruebas están pensadas para identificar problemas menos frecuentes, que suelen necesitar que se modifiquen factores externos (el ping de la red, los FPS, el funcionamiento al usar Alt + TAB, etc.). Equipados con un repertorio de herramientas internas, el equipo de control de calidad se pasó varias semanas con las pruebas destructivas.

    Pruebas de excepciones

    Hay muchos aspectos de VALORANT que solo experimentan un pequeño porcentaje de los jugadores. Algunos de ellos solo se experimentan una vez (por ejemplo, la experiencia para nuevos jugadores). Que estas zonas del juego estén menos transitadas no quiere decir que sean menos importantes. Identificar y hacer pruebas para todos los casos excepcionales nos ayudó a dar con errores ocultos.

    Pruebas en el entorno de la beta pública (PBE)

    La PBE fue un paso muy importante para la invalidación global.

    • Era la primera vez que los jugadores podían probar esta característica. Eso quería decir que podíamos empezar a probarla en "condiciones reales".
    • El entorno de la beta pública reúne una amplia variedad de especificaciones de hardware. El hecho de que cubra las gamas baja, media y alta es algo intencionado, y realizar pruebas con tanta variedad de dispositivos nos permitió confirmar que la invalidación global tendría buenos resultados para nuestros jugadores en general.

    Tras las pruebas del fin de semana del 22 y el 23 de enero de 2022, estábamos seguros de que la invalidación global no interfería con la integridad del juego y que las mejoras de rendimiento coincidían con nuestras estimaciones.

    El lanzamiento de la invalidación global

    Tras lanzar la invalidación global con la versión 4.03, seguimos muy de cerca los informes de errores de los jugadores. También nos mantuvimos muy alerta a los datos de rendimiento para confirmar que nuestras proyecciones encajaban con los resultados. Al final, la invalidación global ha sido un éxito rotundo entre los jugadores, y esperamos que disfrutéis de los tiempos de respuesta mejorados.

    Ahora que esta característica ya está incorporada, el equipo de rendimiento pasará a buscar la próxima gran mejora. Hasta entonces, ¡nos vemos en el campo de batalla!