Heureusement, comme vous l'avez souligné, les versions COMPACT Mono utilisent un GC générationnel (contrairement à celles de Microsoft, comme WinMo / WinPhone / XBox, qui ne font que maintenir une liste plate).
Si votre jeu est simple, le GC devrait le gérer très bien, mais voici quelques conseils que vous voudrez peut-être examiner.
Optimisation prématurée
Assurez-vous d'abord que c'est réellement un problème avant d'essayer de le résoudre.
Mise en commun des types de référence coûteux
Vous devez regrouper les types de référence que vous créez souvent ou qui ont des structures profondes. Un exemple de chacun serait:
- Créé souvent: un
Bullet
objet dans un jeu d'enfer .
- Structure profonde: Arbre de décision pour une implémentation de l'IA.
Vous devez utiliser a Stack
comme pool (contrairement à la plupart des implémentations qui utilisent a Queue
). La raison en est qu'avec un Stack
si vous renvoyez un objet dans le pool et quelque chose d'autre le saisit immédiatement; il aura beaucoup plus de chances d'être dans une page active - ou même dans le cache CPU si vous êtes chanceux. C'est juste un tout petit peu plus rapide. De plus, limitez toujours la taille de vos pools (ignorez simplement les «enregistrements» si votre limite a été dépassée).
Évitez de créer de nouvelles listes pour les effacer
Ne créez pas de nouveau List
quand vous le vouliez réellement Clear()
. Vous pouvez réutiliser le tableau principal et enregistrer une charge d'allocations et de copies de tableau. De la même manière, essayez de créer des listes avec une capacité initiale significative (rappelez-vous, ce n'est pas une limite - juste une capacité de départ) - cela n'a pas besoin d'être précis, juste une estimation. Cela devrait s'appliquer à pratiquement n'importe quel type de collection - à l'exception de a LinkedList
.
Utilisez des tableaux (ou des listes) de structures dans la mesure du possible
Vous gagnez peu d'avantages à utiliser des structures (ou des types de valeurs en général) si vous les passez entre les objets. Par exemple, dans la plupart des «bons» systèmes de particules, les particules individuelles sont stockées dans un réseau massif: le réseau et l'indice sont transmis à la place de la particule elle-même. La raison pour laquelle cela fonctionne si bien est que lorsque le GC doit collecter le tableau, il peut ignorer le contenu entièrement (c'est un tableau primitif - rien à faire ici). Ainsi, au lieu de regarder 10 000 objets, le GC doit simplement regarder 1 tableau: un gain énorme! Encore une fois, cela ne fonctionnera qu'avec les types de valeur .
Après RoyT. a fourni des commentaires viables et constructifs, je pense que je dois développer davantage. Vous ne devez utiliser cette technique que lorsque vous avez affaire à des quantités massives d'entités (des milliers à des dizaines de milliers). De plus, ce doit être une structure qui ne doit pas avoir de champs de type référence et doit vivre dans un tableau explicitement typé. Contrairement à ses commentaires, nous le plaçons dans un tableau qui est très probablement un champ dans une classe - ce qui signifie qu'il va atterrir le tas (nous n'essayons pas d'éviter une allocation de tas - simplement en évitant le travail GC). Nous nous soucions vraiment du fait qu'il s'agit d'un bloc de mémoire contigu avec beaucoup de valeurs que le GC peut simplement regarder dans une O(1)
opération au lieu d'une O(n)
opération.
Vous devez également allouer ces tableaux le plus près possible du démarrage de votre application pour atténuer les risques de fragmentation ou de travail excessif lorsque le GC essaie de déplacer ces morceaux (et envisagez d'utiliser une liste liée hybride au lieu du List
type intégré ). ).
GC.Collect ()
C'est définitivement LA MEILLEURE façon de vous tirer une balle dans le pied (voir: "Considérations de performances") avec un GC générationnel. Vous ne devriez l'appeler que lorsque vous avez créé une quantité EXTRÊME de déchets - et la seule instance où cela pourrait être un problème est juste après avoir chargé le contenu pour un niveau - et même alors, vous ne devriez probablement collecter que la première génération ( GC.Collect(0);
) avec un peu de chance pour empêcher la promotion d'objets à la troisième génération.
IDisposable et Field Nulling
Il vaut la peine d'annuler les champs lorsque vous n'avez plus besoin d'un objet (plus encore sur les objets contraints). La raison en est dans les détails du fonctionnement du GC: il supprime uniquement les objets qui ne sont pas enracinés (c.-à-d. Référencés) même si cet objet aurait été non raciné en raison du retrait d'autres objets dans la collection actuelle ( remarque: cela dépend du GC saveur utilisée - certains nettoient les chaînes). De plus, si un objet survit à une collection, il est immédiatement promu à la génération suivante - cela signifie que tout objet laissé traîner dans les champs sera promu lors d'une collection. Chaque génération successive est exponentiellement plus coûteuse à collecter (et se produit aussi rarement).
Prenons l'exemple suivant:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// G1 Collection
MyNestObject (G2) -> MyFurtherNestedObject (G2)
// G2 Collection
MyFurtherNestedObject (G3)
S'il MyFurtherNestedObject
contient un objet de plusieurs mégaoctets, vous pouvez être assuré que le GC ne le regardera pas assez longtemps - parce que vous l'avez par inadvertance promu en G3. Comparez cela avec cet exemple:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// Dispose
MyObject (G1)
MyNestedObject (G1)
MyFurtherNestedObject (G1)
// G1 Collection
Le modèle Disposer vous aide à configurer un moyen prévisible de demander aux objets d'effacer leurs champs privés. Par exemple:
public class MyClass : IDisposable
{
private MyNestedType _nested;
// A finalizer is only needed IF YOU CONTROL UNMANAGED RESOURCES
// ~MyClass() { }
public void Dispose()
{
_nested = null;
}
}