Gestire la memoria in modo efficace nei giochi

Sulla piattaforma Android, il sistema tenta di utilizzare quanta più memoria di sistema (RAM) possibile ed esegue varie ottimizzazioni della memoria per liberare spazio quando necessario. Queste ottimizzazioni possono avere un effetto negativo sul gioco, rallentandolo o interrompendolo del tutto. Puoi scoprire di più su queste ottimizzazioni nell'argomento Allocazione della memoria tra i processi.

Questa pagina spiega i passaggi che puoi seguire per evitare che la memoria insufficiente influisca sul tuo gioco.

Rispondere a onTrimMemory()

Il sistema utilizza onTrimMemory() per comunicare alla tua app gli eventi del ciclo di vita che rappresentano una buona opportunità per la tua app di ridurre volontariamente l'utilizzo della memoria ed evitare di essere interrotta dal low-memory killer (LMK) per liberare memoria da utilizzare per altre app.

Se la tua app viene chiusa in background, la volta successiva che l'utente la avvia si verifica un avvio completo lento. Le app che riducono l'utilizzo della memoria quando passano in background hanno meno probabilità di essere chiuse in background.

Quando rispondi agli eventi di taglio, è consigliabile rilasciare le allocazioni di memoria di grandi dimensioni che non sono necessarie immediatamente e che potrebbero essere ricostruite su richiesta. Ad esempio, se la tua app ha una cache di bitmap decodificati da immagini compresse archiviate localmente, spesso è una buona idea di tagliare o eliminare questa cache in risposta a TRIM_MEMORY_UI_HIDDEN.

Kotlin

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
    override fun onTrimMemory(level: Int) {
        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }
        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Java

public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
    public void onTrimMemory(int level) {
        switch (level) {
            if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
                // Release memory related to UI elements, such as bitmap caches.
            }
            if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
                // Release memory related to background processing, such as by
                // closing a database connection.
            }
        }
    }
}

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())
    }
}

Fai attenzione ai budget di memoria

Gestisci la memoria del budget in modo conservativo per evitare di esaurirla. Alcuni elementi da prendere in considerazione includono:

  • Dimensioni della RAM fisica: i giochi spesso utilizzano tra ¼ e ½ della quantità di RAM fisica sul dispositivo.
  • Dimensioni massime di zRAM: più zRAM significa che il gioco ha potenzialmente più memoria da allocare. Questo importo può variare in base al dispositivo. Cerca SwapTotal in /proc/meminfo per trovare questo valore.
  • Utilizzo della memoria del sistema operativo: i dispositivi che destinano più RAM ai processi di sistema lasciano meno memoria per il gioco. Il sistema termina il processo del gioco prima di terminare i processi di sistema.
  • Utilizzo della memoria delle app installate: testa il gioco su dispositivi con molte app installate. Le app di chat e social media devono essere eseguite costantemente e influiscono sulla quantità di memoria libera.

Se non puoi impegnarti a rispettare un budget di memoria conservativo, adotta un approccio più flessibile. Se il sistema riscontra problemi di memoria insufficiente, riduci la quantità di memoria utilizzata dal gioco. Ad esempio, alloca texture a risoluzione inferiore o memorizza meno shader in risposta a onTrimMemory(). Questo approccio dinamico all'allocazione della memoria richiede più lavoro da parte dello sviluppatore, soprattutto nella fase di progettazione del gioco.

Evitare l'eccessivo utilizzo del disco

Il thrashing si verifica quando la memoria libera è scarsa, ma non abbastanza da chiudere il gioco. In questa situazione, kswapd ha recuperato le pagine di cui il gioco ha ancora bisogno, quindi tenta di ricaricarle dalla memoria. Lo spazio non è sufficiente, quindi le pagine vengono scambiate continuamente (scambio continuo). System tracing segnala questa situazione come un thread in cui kswapd viene eseguito continuamente.

Un sintomo di thrashing sono tempi di frame lunghi, forse un secondo o più. Riduci l'utilizzo di memoria del gioco per risolvere il problema.

Utilizzare gli strumenti disponibili

Android dispone di una raccolta di strumenti per comprendere come il sistema gestisce la memoria.

Meminfo

Questo strumento raccoglie statistiche sulla memoria per mostrare la quantità di memoria PSS allocata e le categorie per cui è stata utilizzata.

Stampa le statistiche meminfo in uno dei seguenti modi:

  • Utilizza il comando adb shell dumpsys meminfo package-name.
  • Utilizza la chiamata MemoryInfo dall'API Android Debug.

La statistica PrivateDirty mostra la quantità di RAM all'interno del processo che non può essere paginata su disco e non è condivisa con altri processi. La maggior parte di questo importo diventa disponibile per il sistema quando il processo viene interrotto.

Punti di traccia della memoria

I punti di traccia della memoria monitorano la quantità di memoria RSS utilizzata dal gioco. Il calcolo dell'utilizzo della memoria RSS è molto più veloce del calcolo dell'utilizzo della memoria PSS. Poiché è più veloce da calcolare, RSS mostra una granularità più fine delle variazioni delle dimensioni della memoria per misurazioni più accurate dell'utilizzo massimo della memoria. Pertanto, è più facile notare i picchi che potrebbero causare l'esaurimento della memoria del gioco.

Perfetto e tracce lunghe

Perfetto è una suite di strumenti per raccogliere informazioni su prestazioni e memoria su un dispositivo e visualizzarle in un'interfaccia utente basata sul web. Supporta tracce di lunghezza arbitraria, in modo da poter visualizzare l'evoluzione dell'RSS nel tempo. Puoi anche eseguire query SQL sui dati che produce per l'elaborazione offline. Attiva le tracce lunghe dall'app System Tracing. Assicurati che la categoria memory:Memory sia attivata per la traccia.

heapprofd

heapprofd è uno strumento di monitoraggio della memoria che fa parte di Perfetto. Questo strumento può aiutarti a trovare perdite di memoria mostrando dove è stata allocata la memoria utilizzando malloc. heapprofd può essere avviato utilizzando uno script Python e, poiché lo strumento ha un basso overhead, non influisce sulle prestazioni come altri strumenti come Malloc Debug.

bugreport

bugreport è uno strumento di logging per scoprire se il gioco è andato in crash perché ha esaurito la memoria. L'output dello strumento è molto più dettagliato rispetto all'utilizzo di logcat. È utile per il debug della memoria perché mostra se il gioco si è arrestato in modo anomalo perché la memoria è esaurita o se è stato interrotto da LMK.

Per ulteriori informazioni, vedi Acquisire e leggere i report sui bug.