Cómo configurar el registro del sistema

Puedes configurar el registro del sistema para capturar un perfil de CPU y subproceso de tu app durante un período breve. Luego, puedes usar el informe de resultados de un registro del sistema para mejorar el rendimiento de tu juego.

Configura un registro del sistema basado en juegos

La herramienta Systrace está disponible de dos maneras:

Systrace es una herramienta de bajo nivel que tiene las siguientes características:

  • Proporciona verdades fundamentales. Systrace captura los resultados directamente del kernel, por lo que las métricas que obtiene son prácticamente idénticas a las que informarían una serie de llamadas al sistema.
  • Consume pocos recursos. Systrace presenta una sobrecarga muy baja para el dispositivo (por lo general, inferior al 1%) debido a que transmite datos dentro de un búfer de la memoria.

Configuración óptima

Es importante brindarle a la herramienta un conjunto razonable de argumentos:

  • Categorías: El mejor conjunto de categorías que permitirá realizar un registro del sistema basado en el juego es el siguiente: {sched, freq, idle, am, wm, gfx, view, sync, binder_driver, hal, dalvik}.
  • Tamaño del búfer: Como regla general, un tamaño del búfer de 10 MB por núcleo de CPU permite un registro de aproximadamente 20 segundos de duración. Por ejemplo, si un dispositivo tiene dos CPU con cuatro núcleos (8 núcleos en total), un valor adecuado para pasar al programa systrace sería 80,000 KB (80 MB).

    Si tu juego realiza una gran cantidad de cambios de contexto, aumenta el búfer a 15 MB por núcleo de CPU.

  • Eventos personalizados: Si defines eventos personalizados para capturarlos en tus juegos, habilita la marca -a, que le permite a Systrace incluir estos eventos personalizados en el informe de resultados.

Si usas el programa de línea de comandos systrace, utiliza el siguiente comando para capturar un registro del sistema que aplique prácticas recomendadas para el conjunto de categorías, el tamaño del búfer y los eventos personalizados:

python systrace.py -a com.example.myapp -b 80000 -o my_systrace_report.html \
  sched freq idle am wm gfx view sync binder_driver hal dalvik

Si usas la app del sistema de Systrace en un dispositivo, completa los siguientes pasos para capturar un registro del sistema que aplique prácticas recomendadas para el conjunto de categorías, el tamaño del búfer y los eventos personalizados:

  1. Habilita la opción Registrar aplicaciones depurables.

    Para usar esta opción, el dispositivo debe tener 256 MB o 512 MB disponibles (dependiendo de si la CPU tiene 4 u 8 núcleos), y cada porción de memoria de 64 MB debe estar disponible como fragmento contiguo.

  2. Elige Categorías y habilita las categorías de la siguiente lista:

    • am: Administrador de actividades
    • binder_driver: Controlador del kernel de Binder
    • dalvik: Máquina virtual Dalvik
    • freq: Frecuencia de CPU
    • gfx: Gráficos
    • hal: Módulos de hardware
    • idle: CPU inactiva
    • sched: Programación de CPU
    • sync: Sincronización
    • view: Sistema de vistas
    • wm: Administrador de ventanas
  3. Habilita la opción Seguimiento de registros.

  4. Carga tu juego.

  5. Realiza las interacciones de tu juego que correspondan al rendimiento del dispositivo que quieres medir.

  6. Después de encontrar el comportamiento no deseado en tu juego, desactiva el registro del sistema.

Obtuviste las estadísticas de rendimiento necesarias para analizar el problema en profundidad.

Para ahorrar espacio en el disco, los registros del sistema integrados en el dispositivo guardan archivos en un formato de registro comprimido (*.ctrace). Para descomprimir el archivo cuando se genere un informe, usa el programa de línea de comandos y agrega la opción --from-file:

python systrace.py --from-file=/data/local/traces/my_game_trace.ctrace \
  -o my_systrace_report.html

Mejora áreas de rendimiento específicas

En esta sección, se destacan varias cuestiones de rendimiento comunes de los juegos para dispositivos móviles, y se describe cómo identificar y mejorar esos aspectos en tu juego.

Velocidad de carga

Los jugadores quieren sumergirse en la acción del juego lo antes posible, por lo que es importante que mejores los tiempos de carga de tu juego tanto como puedas. Por lo general, las siguientes medidas ayudan a hacerlo:

  • Realiza una carga diferida. Si usas los mismos elementos en escenas o niveles consecutivos de tu juego, cárgalos solo una vez.
  • Reduce el tamaño de tus elementos. De esa forma, puedes empaquetar versiones sin comprimir de estos elementos con el APK del juego.
  • Usa un método de compresión que haga un uso eficiente del espacio del disco. Por ejemplo, zlib.
  • Usa IL2CPP en lugar de mono. (Solo se aplica si utilizas Unity). IL2CPP ofrece un mejor rendimiento de ejecución para tus secuencias de comandos C#.
  • Haz que tu juego sea multiproceso. Para obtener información detallada, consulta la sección de constancia de la velocidad de fotogramas.

Constancia de la velocidad de fotogramas

Uno de los aspectos más importantes de la experiencia de juego es lograr una velocidad de fotogramas constante. A fin de lograrlo, sigue las técnicas de optimización que se mencionan en esta sección.

Multiproceso

Cuando se desarrollan juegos para varias plataformas, es normal colocar toda la actividad del juego en un solo proceso. Si bien este método de ejecución es fácil de implementar en muchos motores de juegos, no resulta óptimo cuando se ejecutan los juegos en dispositivos Android. Como resultado, los juegos de un solo proceso suelen cargarse lentamente y no tienen una velocidad de fotogramas constante.

El informe de Systrace incluido en la Figura 1 muestra el comportamiento típico de un juego que se ejecuta en una sola CPU a la vez:

Diagrama de subprocesos dentro de un registro del sistema

Figura 1: Informe de Systrace para un juego de un solo proceso

Para mejorar el rendimiento de tu juego, asegúrate de que tu juego sea multiproceso. Por lo general, el mejor modelo es el de 2 subprocesos:

  • Un subproceso de juego, que contenga los módulos principales del juego y envíe comandos de procesamiento
  • Un subproceso de procesamiento, que reciba los comandos de procesamiento y los traduzca en comandos de gráfico que una GPU del dispositivo puede usar para mostrar una escena

La API de Vulkan amplía este modelo gracias a su capacidad de actualizar 2 búferes comunes en paralelo. Con esta función, puedes distribuir los subprocesos de procesamiento en varias CPU, lo que mejora el tiempo de procesamiento de una escena.

También puedes realizar los siguientes cambios específicos del motor a fin de mejorar el rendimiento multiproceso:

  • Si vas a desarrollar tu juego con el motor Unity, habilita las opciones Multithreaded Rendering y GPU Skinning.
  • Si usas un motor de procesamiento personalizado, asegúrate de que la canalización de comandos de procesamiento y la canalización de comandos de gráficos estén alineadas correctamente; de lo contrario, podrían producirse retrasos en la visualización de las escenas del juego.

Después de aplicar los cambios, deberías ver que tu juego ocupa al menos 2 CPU en simultáneo, como se muestra en la Figura 2.

Diagrama de subprocesos dentro de un registro del sistema

Figura 2: Informe de Systrace para juego multiproceso

Carga de elementos de IU

Diagrama de pila de fotogramas dentro de un registro del sistema
Figura 3: Informe de Systrace para un juego que procesa decenas de elementos de la IU al mismo tiempo

Cuando se crea un juego con muchas funciones, resulta tentador mostrarle varias opciones y acciones al jugador al mismo tiempo. A fin de mantener la velocidad de fotogramas constante, es importante tener en cuenta el tamaño pequeño de las pantallas de los dispositivos móviles y crear una IU lo más simple posible.

El informe de Systrace que se muestra en la Figura 3 es un ejemplo de fotograma de IU que intenta procesar demasiados elementos para la capacidad de un dispositivo móvil.

Un buen objetivo es reducir el tiempo de actualización de IU a 2 o 3 milisegundos. Para lograr una actualización tan rápida, puedes realizar optimizaciones similares a las siguientes:

  • Actualiza solo los elementos en pantalla que se hayan movido.
  • Limita la cantidad de texturas y capas de IU. Considera combinar llamadas de gráficos, como sombreadores y texturas, que usen el mismo material.
  • Difiere las operaciones de animación de elementos a la GPU.
  • Realiza un frustum y aprovechamiento de oclusión más pronunciados.
  • Si es posible, realiza operaciones de dibujo con la API de Vulkan. La sobrecarga de llamada de dibujo es más baja en Vulkan.

Consumo de energía

Incluso después de realizar las optimizaciones mencionadas en la sección anterior, es posible que observes que la velocidad de fotogramas de tu juego se deteriora en el transcurso de los primeros 45 o 50 minutos de juego. Además, el dispositivo podría comenzar a recalentarse y consumir más batería con el tiempo.

En muchos casos, este aumento no deseado de temperatura y consumo de energía está relacionado con la manera en que se distribuye la carga de trabajo del juego en las CPU del dispositivo. Para aumentar la eficiencia de consumo de energía de tu juego, aplica las prácticas recomendadas que aparecen en las siguientes secciones.

Mantén los subprocesos que consumen mucha memoria en una CPU

En muchos dispositivos móviles, las caché L1 residen en CPU específicas, y las caché L2 en un conjunto de CPU que comparten un reloj. A fin de maximizar los aciertos de la caché L1, te recomendamos que mantengas el subproceso principal de tu juego, junto con los subprocesos que consumen mucha memoria, ejecutándose en una sola CPU.

Difiere el trabajo de poca duración a CPU con poco consumo de energía

La mayoría de los motores de juegos, incluido Unity, saben diferir las operaciones de subprocesos de trabajo a diferentes CPU en cuanto al subproceso principal del juego. Sin embargo, el motor no conoce la arquitectura específica del dispositivo y no puede anticipar la carga de trabajo de tu juego tan bien como tú.

La mayoría de los dispositivos con sistema en chip tienen al menos 2 relojes compartidos: uno para las CPU rápidas y otro para las CPU lentas del dispositivo. Una consecuencia de esta arquitectura es que, si una CPU rápida necesita operar a máxima velocidad, todas las demás CPU operan a máxima velocidad.

El informe de ejemplo de la Figura 4 muestra un juego que aprovecha las CPU rápidas. Sin embargo, este alto nivel de actividad genera un alto consumo de energía y hace que el dispositivo recaliente con rapidez.

Diagrama de subprocesos dentro de un registro del sistema

Figura 4: Informe de Systrace que muestra una asignación por debajo del nivel óptimo de subprocesos a las CPU del dispositivo

Para reducir el consumo general de energía, te recomendamos que le sugieras al programador que se difiera el trabajo de menor duración, como la carga de audio, la ejecución de los subprocesos de trabajo y la ejecución del coreógrafo, al conjunto de CPU lentas del dispositivo. Transfiere tanta carga como sea posible a las CPU lentas a fin de mantener la velocidad de fotogramas deseada.

La mayoría de los dispositivos muestran las CPU lentas antes que las rápidas, pero no puedes asumir que el SoC de tu dispositivo las usa en ese orden. Para comprobarlo, ejecuta comandos similares a los que se muestran en este código de descubrimiento de topología de CPU en GitHub.

Después de saber cuáles son las CPU lentas de tu dispositivo, puedes declarar afinidades para tus subprocesos de menor duración, y el programador del dispositivo las seguirá. Para ello, agrega el siguiente código a cada subproceso:

#include <sched.h>
#include <sys/types.h>
#include <unistd.h>

pid_t my_pid; // PID of the process containing your thread.

// Assumes that cpu0, cpu1, cpu2, and cpu3 are the "slow CPUs".
cpu_set_t my_cpu_set;
CPU_ZERO(&my_cpu_set);
CPU_SET(0, &my_cpu_set);
CPU_SET(1, &my_cpu_set);
CPU_SET(2, &my_cpu_set);
CPU_SET(3, &my_cpu_set);
sched_setaffinity(my_pid, sizeof(cpu_set_t), &my_cpu_set);

Aumento considerable de la temperatura

Cuando los dispositivos se sobrecalientan, pueden acelerar la CPU o la GPU, lo que puede afectar a las apps y los juegos de forma inesperada. Los juegos que incorporan gráficos complejos, procesamiento intensivo o actividad de red sostenida tienen más probabilidades de encontrar problemas.

Usa la API térmica a fin de supervisar los cambios de temperatura en el dispositivo y tomar medidas para mantener un consumo de energía y una temperatura del dispositivo más bajos. Cuando el dispositivo informa sobre un aumento considerable de la temperatura, interrumpe las actividades en curso para reducir el uso de energía. Por ejemplo, reduce la velocidad de fotogramas o el teselado poligonal.

Primero, declara el objeto PowerManager e inicialízalo en el método onCreate(). Agrégale un objeto de escucha de estado térmico al objeto.

Kotlin

class MainActivity : AppCompatActivity() {
    lateinit var powerManager: PowerManager

    override fun onCreate(savedInstanceState: Bundle?) {
        powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
        powerManager.addThermalStatusListener(thermalListener)
    }
}

Java

public class MainActivity extends AppCompatActivity {
    PowerManager powerManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        powerManager.addThermalStatusListener(thermalListener);
    }
}

Define las acciones que se deben realizar cuando el objeto de escucha detecta un cambio de estado. Si tu juego usa C/C++, agrega código a los niveles del estado térmico en onThermalStatusChanged() para llamar a tu código de juego nativo con JNI o usar la API térmica nativa.

Kotlin

val thermalListener = object : PowerManager.OnThermalStatusChangedListener() {
    override fun onThermalStatusChanged(status: Int) {
        when (status) {
            PowerManager.THERMAL_STATUS_NONE -> {
                // No thermal status, so no action necessary
            }

            PowerManager.THERMAL_STATUS_LIGHT -> {
                // Add code to handle light thermal increase
            }

            PowerManager.THERMAL_STATUS_MODERATE -> {
                // Add code to handle moderate thermal increase
            }

            PowerManager.THERMAL_STATUS_SEVERE -> {
                // Add code to handle severe thermal increase
            }

            PowerManager.THERMAL_STATUS_CRITICAL -> {
                // Add code to handle critical thermal increase
            }

            PowerManager.THERMAL_STATUS_EMERGENCY -> {
                // Add code to handle emergency thermal increase
            }

            PowerManager.THERMAL_STATUS_SHUTDOWN -> {
                // Add code to handle immediate shutdown
            }
        }
    }
}

Java

PowerManager.OnThermalStatusChangedListener thermalListener =
    new PowerManager.OnThermalStatusChangedListener () {

    @Override
    public void onThermalStatusChanged(int status) {

        switch (status)
        {
            case PowerManager.THERMAL_STATUS_NONE:
                // No thermal status, so no action necessary
                break;

            case PowerManager.THERMAL_STATUS_LIGHT:
                // Add code to handle light thermal increase
                break;

            case PowerManager.THERMAL_STATUS_MODERATE:
                // Add code to handle moderate thermal increase
                break;

            case PowerManager.THERMAL_STATUS_SEVERE:
                // Add code to handle severe thermal increase
                break;

            case PowerManager.THERMAL_STATUS_CRITICAL:
                // Add code to handle critical thermal increase
                break;

            case PowerManager.THERMAL_STATUS_EMERGENCY:
                // Add code to handle emergency thermal increase
                break;

            case PowerManager.THERMAL_STATUS_SHUTDOWN:
                // Add code to handle immediate shutdown
                break;
        }
    }
};

Latencia de la opción tocar para ver

Los juegos que procesan fotogramas lo más rápido posible crean una situación de vinculación con la GPU, en la que el búfer de fotogramas se sobrecarga. La CPU debe esperar a la GPU, lo que provoca un notorio retraso cuando el jugador realiza una entrada y esta surte efecto en la pantalla.

Para determinar si podrías mejorar el ritmo de velocidad de fotogramas de tu juego, completa los siguientes pasos:

  1. Genera un informe de Systrace que incluya las categorías gfx y input. Estas categorías comprenden medidas que resultan particularmente útiles si deseas determinar la latencia de la función tocar para ver.
  2. Revisa la sección SurfaceView de un informe de Systrace. Un búfer sobrecargado hace que la cantidad de dibujos de búfer pendientes oscile entre 1 y 2, como se muestra en la Figura 5.

    Diagrama de la cola de búfer dentro de un registro del sistema

    Figura 5: Informe de Systrace que muestra un búfer sobrecargado que suele estar demasiado lleno como para aceptar comandos de dibujo

Para mitigar esta falta de constancia en el ritmo de fotogramas, completa las acciones que se describen en las siguientes secciones:

Integra la API de ritmo de fotogramas de Android a tu juego

La API de Android Frame Pacing te ayuda a realizar intercambios de fotogramas y definir un intervalo de intercambio para que tu juego mantenga una velocidad de fotogramas más constante.

Reduce la resolución de los elementos que no son IU de tu juego

Las pantallas de los dispositivos modernos contienen muchos más píxeles de los que puede procesar un jugador, por lo que está bien reducir el muestreo de manera que una ejecución de 5 o 10 píxeles contenga un color. Debido a la estructura de la mayoría de las caché, se recomienda reducir la resolución junto con una dimensión solamente.

Sin embargo, no debes reducir la resolución de los elementos de IU de tu juego. Es importante conservar el espesor de las líneas de esos elementos para mantener un tamaño objetivo del elemento táctil lo suficientemente grande para todos tus jugadores.

Suavidad de procesamiento

Cuando SurfaceFlinger se conecta a un búfer de pantalla para mostrar una escena de tu juego, la actividad de CPU aumenta temporalmente. Si estos picos de actividad de CPU no se producen de manera pareja, es posible que veas trabas en tu juego. El diagrama de la Figura 6 muestra el motivo:

Diagrama de fotogramas al que le falta una ventana de Vsync porque comenzó a procesar demasiado tarde

Figura 6: Informe de Systrace que muestra la manera en que un fotograma puede perder un Vsync

Si un fotograma comienza a dibujarse demasiado tarde, incluso por unos milisegundos, podría perderse la siguiente ventana de visualización. Entonces, el fotograma debe esperar a que se muestre el próximo Vsync (33 milisegundos cuando se ejecuta un juego a 30 FPS), lo que produce un notable retraso desde la perspectiva del jugador.

Para resolver esta situación, usa la API de Android Frame Pacing, que siempre presenta un nuevo fotograma en un frente de onda de Vsync.

Estado de la memoria

Cuando tu juego se ejecuta durante un período prolongado, es posible que el dispositivo tenga errores de falta de memoria.

Es esa situación, comprueba la actividad de CPU en un informe de Systrace y consulta la frecuencia con la que el sistema realiza llamadas al daemon kswapd. Si hay muchas llamadas durante la ejecución del juego, te recomendamos que observes la manera en que tu juego administra y limpia la memoria.

Para obtener más información, consulta Cómo administrar de manera eficaz la memoria en juegos.

Estado de subprocesos

Cuando navegas por los elementos típicos de un informe de Systrace, puedes ver la cantidad de tiempo que un subproceso determinado dedica en cada posible estado del subproceso. Para ello, selecciona el subproceso en el informe, como se muestra en la Figura 7:

Diagrama de un informe de Systrace

Figura 7: Informe de Systrace que muestra cómo la selección de un subproceso hace que el informe muestre un resumen de estado para ese subproceso

Como se muestra en la Figura 7, es posible que veas que los subprocesos de tu juego no se encuentran en estado "en ejecución" o "ejecutable" con la frecuencia que deberían. En la siguiente lista, se muestran varios motivos comunes por los cuales un subproceso determinado podría pasar en forma periódica a un estado inusual:

  • Si un subproceso se encuentra inactivo durante mucho tiempo, es posible que esté experimentando una competencia de bloqueo o esperando a la actividad de GPU.
  • Si un subproceso está bloqueado constantemente en I/O, quiere decir que estás leyendo demasiados datos de un disco al mismo tiempo o que el juego sufre de hiperpaginación.

Recursos adicionales

Para obtener más información sobre cómo mejorar el rendimiento de tu juego, consulta los siguientes recursos adicionales:

Videos