Modèles d'allocation de mémoire utilisés dans le développement de jeux

20

J'ai fait des recherches sur la création de mes propres méthodes d'allocation (qui prendront en charge des éléments tels qu'un pool de mémoire et le profilage), cependant, alors que je continue mes recherches, j'ai cherché comment cela a été fait dans le développement de jeux.

Quelle technique d'allocation de mémoire puis-je utiliser et pourquoi est-ce une bonne technique?

chadb
la source
1
en avez-vous vraiment besoin? c'est juste l'une des choses les plus compliquées qu'une équipe puisse implémenter, si elle peut l'implémenter.
Ali1S232
4
C'est un domaine d'intérêt pour moi, donc j'aimerais en savoir plus et le mettre en œuvre
chadb
Je dois dire que le sujet est vraiment intéressant ... Il y a des cas où cela peut signifier attribuer, mais sur un jeu PC moyen, je préfère m'inquiéter du jeu réel ...
rioki
Avez-vous recherché le code source de la bibliothèque standard moderne malloc et gratuit ou nouveau et supprimer? Je pose la question car il semble que cela fournirait une base très utile pour comparer toute stratégie d'allocation alternative à l'algorithmique ou à la pratique. Il semble que cela fournirait également un aperçu réel de ce que vous allez faire.
Louis Langholtz

Réponses:

25

Game Engine Architecture contient des informations sur ce sujet. Les bases sont que vous devez faire une analyse pour comprendre vos besoins en mémoire par niveau / trame / etc. sont comme, mais il y a quelques modèles que l'auteur mentionne avoir vu plusieurs fois:

  • Allocateurs basés sur la pile: ceux- ci allouent une fois un grand segment de mémoire, puis allouent des pointeurs dans ce bloc de mémoire en réponse à des demandes provenant d'ailleurs dans le jeu. Ceci est utile pour éviter les changements de contexte requis par l'allocation de mémoire, et aussi parce que vous pouvez utiliser vos propres techniques pour appliquer la contiguïté ou l'alignement spécifique pour les opérations SIMD. Certains moteurs utilisent également une pile à double extrémité où un type de ressource est chargé par le haut et l'autre est chargé par le bas. Peut-être LSR (Load and Stay Resident, le genre de chose qui sera nécessaire tout au long de votre jeu) du haut et les données par niveau du bas.
  • Mémoire de trame unique ou mémoire de trame à double tampon: Mémoire pour les opérations qui se produisent dans un ou deux cycles de trame. C'est utile car plutôt que d'avoir à allouer / désallouer chaque image, vous pouvez simplement supprimer les données de la dernière image en réinitialisant le pointeur que vous utilisez pour garder une trace de la mémoire jusqu'au début du bloc.
  • Pools d'objets: un bloc de mémoire pour de nombreux objets de même taille, tels que des particules, des ennemis, des projectiles. Ils sont utiles car vous pouvez facilement atteindre la contiguïté en trouvant le premier segment inutilisé de votre pool. Ils facilitent également l'itération, car chaque objet est à un décalage connu du dernier.

La plus grande chose que l'auteur mentionne est la fragmentation de la mémoire. C'est moins un problème si vous développez par exemple pour un PC où vous avez une sorte de sauvegarde de pagination de la mémoire sur laquelle vous pouvez compter, mais dans un contexte de mémoire fixe comme une console, il y a le risque d'être "à court de mémoire" lorsque vous essayez d'allouer pour un grand objet car votre mémoire est fragmentée de telle sorte que seuls de petits blocs contigus sont disponibles. À cette fin, il recommande qu'un allocateur basé sur la pile comme ci-dessus inclue également une méthode de défragmentation périodique de son contenu.

Pour plus d'informations sur le code réel impliqué dans cela, je recommande fortement l'article de Christian Gyrling, "Sommes-nous à court de mémoire?" , qui couvre les techniques d'allocateurs personnalisés, principalement du point de vue de l'analyse des modèles d'utilisation de la mémoire, mais cela s'applique également à la conception d'une solution personnalisée pour la gestion de la mémoire.


la source
1

D'après ce que j'ai vu (mais pas fait), chaque jeu a tendance à hériter des mécanismes d'allocation d'un framework, d'un moteur de jeu, de la version précédente (2010 -> 2011) ou il obtient un ensemble de nouveaux écrits spécifiquement pour son (lorsque les structures de données sont réutilisables et de taille fixe ou de nombreux types et tailles variables).

Nous avions également des allocateurs différents pour les fichiers / composants sonores que pour les niveaux et autres objets de jeu dans le même projet. Dans d'autres projets, les allocateurs sont hérités des bibliothèques externes uniquement pour les composants gérés par cette bibliothèque.

L'optimisation dépend vraiment de vos besoins. Mais généralement, l'allocation est effectuée avant d'entrer sur la scène du jeu, puis la mémoire est réutilisée. Certains jeux peuvent s'en tirer sans avoir d'allocateurs personnalisés. Mais pour les jeux d'action où le processeur, la mémoire et les ressources de données sont budgétisés, vous ne pouvez pas vous permettre de perdre du temps de traitement sur de grandes allocations, vous ne pouvez pas gaspiller la mémoire en fragmentation et autres problèmes.

Concernant les exemples, vous devriez simplement commencer par jeter un œil au moteur de jeu OGRE 3D , il a quelques options pour configurer les allocateurs de mémoire.

Coyote
la source
0

L'erreur qui est souvent commise est d'écrire vos propres allocateurs afin que vous puissiez avoir plus de contrôle sur la quantité de mémoire utilisée par chaque système et avoir plus de visibilité sur ce qui se passe. Un bien meilleur moyen d'y parvenir est d'utiliser un profileur de mémoire. Il existe de nombreux profileurs de mémoire, mon profileur MemPro en est un exemple. Il s'agit d'un moyen totalement non invasif de garder une trace de toute l'utilisation de la mémoire et vous pouvez la décomposer automatiquement en sous-systèmes à l'aide de filtres génériques de pile d'appels. Idéalement, il est préférable de garder votre allocation de mémoire et votre suivi de la mémoire totalement séparés, ils ont des exigences totalement différentes.

Diviser arbitrairement votre mémoire en pools peut souvent être préjudiciable car chaque pool aura un surcoût. Vous pouvez finir par utiliser beaucoup plus de mémoire que nécessaire sans vous en rendre compte. Pour réduire le gaspillage, il est toujours préférable de tout regrouper, le jeu est alors partagé par l'ensemble du système.

Les seules raisons d'utiliser des allocateurs personnalisés sont les performances du processeur (principalement pour la cohérence du cache) et pour limiter la fragmentation. Un exemple parfait de ceci est un système de particules. Vous voulez toutes les particules contiguës en mémoire et vous ne voulez pas poivrer la mémoire principale avec beaucoup d'allocations de courte durée. Un autre bon exemple de partitionnement est un langage de script.

Si vous voulez un exemple de remplacement de malloc à usage général, vous pouvez jeter un œil à mon allocateur VMem . Il a été utilisé dans un certain nombre de jeux AAA livrés. Il dispose de techniques qui limitent la fragmentation et maintiennent une empreinte mémoire faible, ce qui est essentiel pour les jeux sur console. Il est également très rapide sous forte contention de threads. Mon site Web contient une documentation complète sur ces techniques.

Stewart Lynch
la source