El juego entero se detiene mientras se recupera la memoria ya usada y desechada (basura). Lo característico son tirones a intervalos regulares.
Por qué En cada frame se crean y se desechan cadenas de texto, arrays y listas temporales → Efecto Cuando se acumula basura, el GC detiene el hilo principal para liberarla → En pantalla Tirones regulares cada pocos segundos o cada varias decenas de segundos
A intervalos regulares, Cuando se junta mucha gente
Responsable
Responsable principal Desarrollo de cliente (Equipo de desarrollo)
Tareas (Equipo de desarrollo)
Reducir las asignaciones (evitar concatenar cadenas, LINQ y capturas en lambdas), usar pools de objetos, mantener activado el GC incremental (opción predeterminada desde Unity 2020), forzar el GC por adelantado en momentos en que una pausa no molesta, como las pantallas de carga.
Cifras de referencia
Normalmente de varios ms a 100 ms por recolección, y más en teléfonos de gama baja o en juegos que usan mucha memoria (en la simulación, unos 150–170 ms). El GC de Unity revisa todo el heap cada vez que se ejecuta, así que cuanta más memoria usa el juego, más dura.
En el gráfico
Picos periódicos · Tiempo de frame, momentos de ejecución del GC
Dónde mirar
En una build de desarrollo, los marcadores GC.Collect y GC.Alloc del Profiler de Unity. En Unreal, stat GC y stat Hitches (registra en el log los frames que superan el tiempo fijado en t.HitchFrameTimeThreshold)
Se confirma si
Cada frame con pico tiene un tramo de GC.Collect de duración parecida a la del pico, y los picos se repiten a intervalos regulares de unos segundos a varias decenas de segundos. Donde hay mucha gente, GC.Alloc por frame aumenta
Se descarta si
Si los frames con pico no tienen tramo de GC: “Carga síncrona y compilación de shaders en el hilo principal” o “Carga de renderizado de multitudes”
Se verifica con
Requiere logs y métricas del servidor o el cliente del juego
Para saber más
Es habitual en clientes que usan C#, como los de Unity. Los principales culpables son el código que vuelve a crear en cada frame las cadenas del log de combate, los números de daño y los textos de la UI. Si los tirones solo aparecen donde hay mucha gente, hay código que genera basura en proporción al número de jugadores. El GC incremental reparte la recolección en pequeñas porciones por frame (3 ms por defecto en Unity), pero si el juego genera basura más rápido de lo que se recolecta, al final se detiene de golpe. Unreal Engine también tiene su propio GC, que limpia los objetos del juego que ya no se usan. Depende de la versión del motor y de la configuración, pero con los valores predeterminados se ejecuta aproximadamente cada minuto y puede provocar un tirón por minuto. En los clientes cuyas reglas de juego están escritas en un lenguaje de scripting como Lua, el GC de ese lenguaje también se ejecuta por separado.
Fuentes
Garbage collection modesUnity El GC incremental es la opción predeterminada y reparte la recolección entre varios frames; si se desactiva, el hilo principal se detiene mientras se revisa todo el heap, y la pausa puede llegar a cientos de ms
Scripting.GarbageCollector.CollectIncrementalUnity El objetivo predeterminado del tiempo que el GC incremental usa en cada porción (time slice) es de 3 ms (incrementalTimeSliceNanoseconds)
Profiler markers referenceUnity GC.Collect: tramo en que el código del programa se detiene durante la recolección de basura (de menos de 1 ms a cientos de ms); GC.Alloc: asignación en el heap administrado
Stat Commands in Unreal EngineEpic Games stat GC (estadísticas de recolección de basura), stat Hitches (registra en el log los frames que superan t.HitchFrameTimeThreshold)