Cómo administrar de manera eficaz la memoria en juegos

En la plataforma de Android, el sistema intenta usar tanta cantidad de memoria del sistema (RAM) como sea posible y realiza varias optimizaciones de memoria para liberar espacio cuando es necesario. Estas optimizaciones pueden tener un efecto negativo en tu juego: puede ralentizarlo o detenerlo por completo. Puedes obtener más información sobre estas optimizaciones en el tema Asignación de memoria entre procesos.

En esta página, se explican los pasos que puedes seguir a fin de evitar condiciones de poca memoria que afecten tu juego.

Responde a onTrimMemory()

El sistema usa onTrimMemory() a los efectos de notificar a tu app que la memoria se está agotando y que es posible que la app se detenga. Muchas veces, esta es la única advertencia que recibe tu app. Esta devolución de llamada tiene una latencia alta relacionada con el optimizador de poca memoria (LMK), por lo que es fundamental responderla rápidamente.

En respuesta a esta devolución de llamada, reduce la velocidad, la cantidad y el tamaño de las asignaciones. onTrimMemory() pasa una constante que indica la gravedad, pero debes responder a la primera advertencia, ya que es posible realizar la asignación más rápido de lo que puede reaccionar onTrimMemory().

Kotlin

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
    override fun onTrimMemory(level: Int) {
        when (level) {
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
                ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
                ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> // Respond to low memory condition
            else -> Unit
        }
    }
}

Java

public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
    public void onTrimMemory(int level) {
        switch (level) {
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
              // Respond to low memory condition
                break;
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
              // Respond to low memory condition
                break;
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
              // Respond to low memory condition
                break;
            default:
                break;

C#

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

class LowMemoryTrigger : MonoBehaviour
{
    private void Start()
    {
        Application.lowMemory += OnLowMemory;
    }
    private void OnLowMemory()
    {
        // Respond to low memory condition (e.g., Resources.UnloadUnusedAssets())
    }
}

Cómo usar la versión beta de la API Memory Advice

La API de Memory Advice se desarrolló como una alternativa a onTrimMemory que tiene una recuperación y precisión mucho más altas para predecir LMKs inminentes. Para ello, la API calcula la cantidad de recursos de memoria que están en uso y, luego, notifica a la app cuando se superan ciertos umbrales. La API también puede informar el porcentaje estimado de uso de memoria directamente a tu app. Puedes usar la API de Memory Advice como alternativa a los eventos de onTrimMemory con el fin de administrar la memoria.

Para usar la API de Memory Advice, consulta la guía de introducción.

Sé conservador con los presupuestos de memoria

Invierte la memoria de forma conservadora con el fin de evitar que esta se agote. Los siguientes son algunos elementos que debes tener en cuenta:

  • Tamaño de la memoria RAM física: Los juegos a menudo usan entre ¼ y ½ de la cantidad física de RAM en el dispositivo.
  • Tamaño máximo de zRAM: Una mayor zRAM significa que el juego puede tener más memoria para asignar. Esta cantidad puede variar según el dispositivo. Busca SwapTotal en /proc/meminfo para conocer este valor.
  • Uso de memoria del SO: Los dispositivos que designan más RAM a los procesos del sistema dejan menos memoria para tu juego. El sistema detiene el proceso de tu juego antes de finalizar sus procesos.
  • Uso de memoria de las apps instaladas: Prueba tu juego en dispositivos que tengan instaladas muchas apps. Las apps de redes sociales y chat deben ejecutarse de forma constante y afectar la cantidad de memoria libre.

Si no puedes comprometerte con un presupuesto de memoria conservador, adopta un enfoque más flexible. Si el sistema tiene problemas de memoria baja, reduce la cantidad de memoria que usa el juego. Por ejemplo, asigna texturas de menor resolución o almacena menos sombreadores en respuesta a onTrimMemory(). Este enfoque dinámico relativo a la asignación de memoria requiere más trabajo de parte del desarrollador, en especial durante la fase de diseño del juego.

Evita la hiperpaginación

La hiperpaginación ocurre cuando la memoria libre es baja, pero no lo suficiente para detener el juego. En esta situación, kswapd tendrá páginas recuperadas que el juego aún necesita, por lo que intentará volver a cargarlas desde la memoria. Como no hay suficiente espacio, las páginas se seguirán reemplazando (reemplazo continuo). El registro del sistema informará esta situación como un subproceso en el que kswapd se ejecuta de manera continua.

Un síntoma de la hiperpaginación es la latencia prolongada de fotogramas (quizás un segundo o más). Para resolver esta situación, reduce la huella de memoria del juego.

Usa las herramientas disponibles

Android cuenta con una colección de herramientas que ayudan a comprender la forma en que el sistema administra la memoria.

Meminfo

Esta herramienta recopila estadísticas de memoria a fin de mostrar qué cantidad de memoria PSS se asignó y las categorías para las que se usó.

Imprime las estadísticas de meminfo de una de las siguientes maneras:

  • Usa el comando adb shell dumpsys meminfo package-name:
  • Usa la llamada MemoryInfo de la API de Android Debug.

La estadística PrivateDirty muestra la cantidad de memoria RAM dentro del proceso que no se puede paginar en el disco y que no se comparte con ningún otro proceso. La mayor parte de esta cantidad quedará disponible para el sistema cuando se detenga ese proceso.

Puntos de seguimiento de memoria

Los puntos de seguimiento de memoria hacen un seguimiento de la cantidad de memoria RSS que usa tu juego. El cálculo del uso de memoria RSS es mucho más rápido que el del uso de PSS. Debido a que es más rápido calcularlo, RSS muestra un nivel de detalle mayor de los cambios en el tamaño de la memoria a fin de obtener mediciones más precisas del uso máximo de memoria. Por lo tanto, resulta más fácil advertir los máximos que podrían hacer que el juego se quede sin memoria.

Perfetto y los registros largos

Perfetto es un paquete de herramientas para recopilar información de rendimiento y memoria en un dispositivo y mostrarla en una IU basada en la Web. Admite registros arbitrariamente largos, por lo que puedes ver cómo el RSS cambia con el tiempo. También puedes emitir búsquedas de SQL sobre los datos que produce para su procesamiento sin conexión. Habilita los registros largos desde la app de Registro del sistema. Asegúrate de que la categoría memory:Memory esté habilitada para el registro.

heapprofd

heapprofd es una herramienta de seguimiento de memoria que forma parte de Perfetto. Esta herramienta puede ayudarte a encontrar fugas de memoria, ya que muestra dónde se asignó la memoria por medio de malloc. heapprofd puede iniciarse con una secuencia de comandos de Python y, dado que la herramienta tiene una sobrecarga baja, no afecta el rendimiento como lo hacen otras herramientas como Malloc Debug.

bugreport

bugreport es una herramienta de registro cuyo fin es descubrir si tu juego falló o no por quedarse sin memoria. El resultado de la herramienta es mucho más detallado que si usaras logcat. Resulta útil para la depuración de memoria, ya que muestra si el juego falló porque se quedó sin memoria o si el LMK lo detuvo.

Para obtener más información, consulta Cómo capturar y leer informes de errores.