Réduire la mémoire

Une fois que vous avez compris la gestion de la mémoire Android et configuré des outils pour mesurer l'utilisation de la mémoire de votre jeu, l'étape suivante consiste à la réduire et à l'optimiser activement. Le respect des limites strictes d'Android permet d'éviter que le système ne ferme votre jeu, de réduire les longs temps de démarrage et de garantir le bon fonctionnement de votre jeu sur tous les appareils.

Ce guide fournit des techniques pratiques pour réduire l'espace mémoire utilisé de votre jeu, en se concentrant plus particulièrement sur l'optimisation au niveau des éléments, les configurations spécifiques au moteur et les bonnes pratiques de gestion de mémoire.

Réduire la mémoire dans Unity

En raison de la conception architecturale d'Unity, une fois que les allocateurs de blocs natifs et le tas géré se développent, le moteur a tendance à conserver ces pages de mémoire pour les réutiliser plutôt que de les renvoyer immédiatement au système d'exploitation, même après la publication des éléments. Plus précisément, l'espace d'adressage virtuel (mémoire réservée) reste réservé pendant toute la durée de vie du processus, et la mémoire physique (RSS) n'est pas récupérée immédiatement tant que plusieurs cycles de récupération de mémoire (GC) et de réduction n'ont pas eu lieu. Par conséquent, un pic de mémoire temporaire peut entraîner une inflation de la mémoire résidente pendant une longue période, même après une baisse de l'utilisation réelle. Ce comportement augmente le risque de plantages dus à une mémoire insuffisante sur les appareils bas de gamme et dégrade la stabilité globale de l'exécution.

Par conséquent, l'optimisation de la mémoire Unity doit être abordée selon trois piliers principaux adaptés à ces comportements du moteur :

  • Contrôlez l'utilisation de la mémoire active pour éviter les pics de mémoire.
  • Gérez les formats de texture et les variantes de shader pour vous assurer que les éléments et les ressources natives inutiles ne sont pas instanciés.
  • Refactorisez les structures de code d'exécution pour éliminer les allocations inutiles sur le tas géré afin de minimiser la fréquence de GC et l'expansion du tas.

Pour en savoir plus, consultez Optimisation de la mémoire Unity.

Réduire la mémoire dans Unreal Engine

Dans Unreal Engine, les pipelines de rendu haute fidélité et les graphiques de dépendance d'objets complexes peuvent augmenter considérablement la pression de la mémoire RSS anonyme et de la mémoire sauvegardée dans des fichiers. En particulier, le fait de s'appuyer sur des références matérielles et des hiérarchies d'héritage Blueprint profondes force le chargement en mémoire des éléments connectés inutilisés. De plus, les permutations de shader excessives, les pools de streaming de textures non optimisés et les tables de relocalisation ELF non compressées contribuent à des empreintes mémoire de base élevées, ce qui augmente le risque de terminaisons LMK (Low Memory Killer).

Par conséquent, l'optimisation de la mémoire Unreal Engine doit être abordée selon trois piliers principaux adaptés à ces comportements du moteur :

  • Dissociez les données et la logique, en remplaçant les références matérielles ou fortes par des références logicielles ou faibles.
  • Supprimez les fonctionnalités d'éclairage mobile et les options de permutation inutilisées pour minimiser les objets d'état de pipeline (PSO) et les cibles de rendu redondantes, tout en appliquant la compression ASTC et en personnalisant les pools de streaming de textures avec des profils d'appareil.
  • Activez la compression des tables de relocalisation RELR et APS pour réduire la taille du binaire ELF et l'espace mémoire utilisé physique d'exécution.

Pour en savoir plus, consultez Optimisation de la mémoire Unreal.

Optimisation multiprocessus

L'utilisation de la mémoire des processus mis en cache est exclue du calcul des limites de mémoire, car elle n'a aucun impact sur les applications actives. L'exécution du service dans un processus isolé distinct permet au processus principal de passer à un état mis en cache aussi rapidement que possible, ce qui améliore l'état de votre jeu.

Pour en savoir plus, consultez Suivre l'état et la mémoire des processus, Isoler un processus de service avec Unity, et Isoler un processus de service avec Unreal.

Réduire l'utilisation de la mémoire dans les services perçus par l'utilisateur

Votre jeu peut avoir besoin d'exécuter une logique dans un service perçu par l'utilisateur pour des cas d'utilisation tels que la finalisation d'un téléchargement volumineux ou pour les systèmes de chat vocal en arrière-plan. Ces stratégies peuvent vous aider à gérer et à réduire l'utilisation de la mémoire dans ces scénarios.

Stratégies pour les téléchargements volumineux

Ces stratégies peuvent s'appliquer aux téléchargements volumineux que vous souhaitez poursuivre même après que l'utilisateur a réduit votre jeu.

1. Isoler le processus de téléchargement

Objectif : assurez-vous que le système d'exploitation peut récupérer immédiatement la mémoire que votre application n'utilise pas en effectuant le téléchargement dans un processus distinct, car il est possible que l'allocateur de mémoire conserve les pages du pool de mémoire et maintienne la mémoire RSS anonyme artificiellement élevée même après la libération des tableaux. Lorsque vous mettez fin ou quittez explicitement le service et le processus, la mémoire est renvoyée au pool du système d'exploitation et votre processus principal n'est pas affecté.

  • Dans Unity : déchargez le téléchargement vers un Android natif Service déclaré avec un processus tel que android:process=":downloader" dans un fichier manifeste personnalisé et appelez-le avec le JNI AndroidJavaClass d'Unity. Veillez à mettre fin au processus une fois le téléchargement terminé. Pour obtenir des conseils plus détaillés, consultez Exécuter un service perceptible dans un processus distinct avec Unity.

  • Dans Unreal : déclarez un Android Service personnalisé avec un processus tel que android:process=":downloader" à l'aide d'Unreal Plugin Language et déclenchez-le avec le JNI C++. Veillez à mettre fin au processus une fois le téléchargement terminé. Pour obtenir des conseils plus détaillés, consultez Exécuter un service perceptible dans un processus distinct avec Unreal.

  • Pour Android natif : déclarez un Service dans le AndroidManifest avec un processus tel que android:process=":downloader". Exécutez le téléchargement dans ce processus isolé et appelez Process.killProcess(Process.myPid()) une fois le téléchargement terminé.

Avantage : réduit la durée de conservation de la mémoire et permet au téléchargement de se poursuivre tout en libérant la mémoire utilisée par le processus principal plus volumineux.

2. Diffuser les téléchargements directement sur le disque

Objectif : diffusez les données directement du socket réseau vers le disque à l'aide d'un petit tampon réutilisable de taille fixe plutôt que d'accumuler les réponses réseau dans un grand tableau avant de les écrire.

  • Dans Unity : évitez d'utiliser DownloadHandlerBuffer pour les bundles d'éléments ou les fichiers volumineux, car il alloue un tampon de mémoire natif équivalent à la taille du fichier (mémoire RSS anonyme). Utilisez plutôt DownloadHandlerFile pour diffuser des octets de manière native sur le disque dans un thread en arrière-plan.

  • Dans Unreal : transmettez les blocs de données entrants directement dans un FArchive (archive sauvegardée dans un fichier à l'aide du gestionnaire de fichiers d'Unreal) avec SetResponseBodyReceiveStream() plutôt que d'ajouter des charges utiles d'un IHttpRequest dans un TArray<uint8>.

  • Pour Android natif : transmettez le InputStream à un FileOutputStream à l'aide d'un tampon mis en pool plutôt que d'appeler .readBytes() ou .string() sur une réponse HTTP.

Avantage : réduit l'utilisation maximale de la mémoire

3. Diffuser la décompression des fichiers téléchargés

Objectif : si votre téléchargement est compressé, encapsulez votre flux d'entrée réseau dans un décompresseur de streaming tel que ZipInputStream au lieu de télécharger le fichier, de le charger dans la RAM, puis de l'extraire.

Avantage : réduit l'utilisation maximale de la mémoire

4. Déléguer au système d'exploitation

Objectif : pour éviter de gérer complètement la mémoire en arrière-plan, déléguez le travail aux API natives d'Android.

  • WorkManager est le wrapper moderne et recommandé pour le JobScheduler au niveau du système d'exploitation. Pour Android 14 et versions ultérieures, WorkManager gère automatiquement les téléchargements déclenchés par l'utilisateur en tant que tâche UIDT (User-Initiated Data Transfer). Cette opération s'exécute dans le processus de votre application. Vous devez donc toujours diffuser directement sur le disque pour minimiser l'utilisation de la mémoire. L'UIDT protège votre application contre les plantages dus à une mémoire insuffisante en permettant au système d'exploitation de mettre en pause et de reprendre votre téléchargement en douceur si les ressources système deviennent limitées.

  • DownloadManager s'exécute dans un processus système distinct et n'attribue pas l'utilisation de la mémoire pour le téléchargement à votre application. Votre application recevra une notification de diffusion lorsque le fichier sera téléchargé et prêt.

Avantage : WorkManager permet de gérer les scénarios de mémoire insuffisante, et DownloadManager réduit l'utilisation de la mémoire de votre application.

Stratégies pour les services auxiliaires

Ces stratégies peuvent s'appliquer aux services auxiliaires qu'un jeu exécute en parallèle du processus de jeu principal, comme le chat vocal en arrière-plan.

1. Isoler le processus

Objectif : dissociez la fonctionnalité, par exemple votre solution de chat vocal, du moteur de jeu principal. Par exemple, vous pouvez exécuter la capture du microphone et le streaming réseau dans un service de premier plan Android attribué à un processus distinct (déclaré dans le fichier manifeste, par exemple avec android:process=":voice").

Avantage : lorsque l'application est réduite, le processus de jeu principal lourd peut passer à un état mis en cache de priorité inférieure, tandis que le service auxiliaire plus léger continue dans l'état de service perçu par l'utilisateur.

2. Réduire la mémoire inutilisée dans le processus

Objectif : si le service auxiliaire est trop profondément intégré au moteur de jeu pour être séparé, essayez de réduire autant que possible le poids du processus dès que le jeu est mis en arrière-plan ou réduit. Envisagez de vider les caches de textures, de décharger les scènes non essentielles, de réduire les taux de rendu et de tick du moteur à 0 et d'appeler explicitement la récupération de mémoire.

Avantage : réduit l'utilisation de la mémoire qui n'est pas nécessaire lorsque le jeu n'est pas au premier plan.