J'allais mettre en place un pool d'objets pour mon système de particules en Java, je trouve cela sur Wikipedia. Pour reformuler, il dit que les pools d'objets ne valent pas la peine d'être utilisés dans des langages gérés comme Java et C #, car les allocations ne prennent que des dizaines d'opérations par rapport à des centaines dans des langages non gérés comme C ++.
Mais comme nous le savons tous, chaque instruction peut nuire aux performances du jeu. Par exemple, un pool de clients dans un MMO: les clients n'entreront pas et ne sortiront pas trop rapidement du pool. Mais les particules peuvent se renouveler des dizaines de fois en une seconde.
La question est: vaut-il la peine d'utiliser un pool d'objets pour les particules (en particulier celles qui meurent et se recréent rapidement) dans un langage géré?
la source
Pour Java, il n'est pas si utile de regrouper des objets *, car le premier cycle GC pour les objets encore autour les remaniera en mémoire, les déplaçant hors de l'espace "Eden" et perdant potentiellement la localité spatiale dans le processus.
Java offre une allocation rapide en rafale à l'aide d'un allocateur séquentiel lorsque vous allouez rapidement des objets dans l'espace Eden. Cette stratégie d'allocation séquentielle est super rapide, plus rapide qu'en
malloc
C car elle regroupe simplement la mémoire déjà allouée de manière séquentielle directe, mais elle présente l'inconvénient que vous ne pouvez pas libérer des morceaux de mémoire individuels. C'est aussi une astuce utile en C si vous voulez simplement allouer des choses super rapidement pour, disons, une structure de données où vous n'avez pas besoin d'en supprimer quoi que ce soit, ajoutez simplement tout, puis utilisez-le et jetez le tout plus tard.En raison de cet inconvénient de ne pas pouvoir libérer des objets individuels, le GC Java, après un premier cycle, copiera toute la mémoire allouée depuis l'espace Eden vers de nouvelles régions de mémoire en utilisant un allocateur de mémoire plus lent et plus général qui permet à la mémoire de être libéré en morceaux individuels dans un thread différent. Ensuite, il peut jeter la mémoire allouée dans l'espace Eden dans son ensemble sans se soucier des objets individuels qui ont maintenant été copiés et vivent ailleurs dans la mémoire. Après ce premier cycle GC, vos objets peuvent finir par être fragmentés en mémoire.
Étant donné que les objets peuvent finir par être fragmentés après ce premier cycle de GC, les avantages du regroupement d'objets lorsqu'il s'agit principalement d'améliorer les modèles d'accès à la mémoire (localité de référence) et de réduire les frais généraux d'allocation / désallocation sont largement perdus ... que vous obtiendrez généralement une meilleure localité de référence en allouant simplement de nouvelles particules tout le temps et en les utilisant pendant qu'elles sont encore fraîches dans l'espace Eden et avant qu'elles ne deviennent "anciennes" et potentiellement dispersées dans la mémoire. Cependant, ce qui peut être extrêmement utile (comme obtenir des performances rivalisant avec C en Java) est d'éviter d'utiliser des objets pour vos particules et de regrouper de vieilles données primitives simples. Pour un exemple simple, au lieu de:
Faites quelque chose comme:
Maintenant, pour réutiliser la mémoire pour les particules existantes, vous pouvez le faire:
Maintenant, quand le
nth
particule meurt, pour permettre sa réutilisation, poussez-la dans la liste gratuite comme suit:Lors de l'ajout d'une nouvelle particule, voyez si vous pouvez faire apparaître un index dans la liste gratuite:
Ce n'est pas le code le plus agréable à utiliser, mais avec cela, vous devriez pouvoir obtenir des simulations de particules très rapides avec un traitement séquentiel des particules très convivial pour le cache, car toutes les données de particules seront toujours stockées de manière contiguë. Ce type de représentant SoA réduit également l'utilisation de la mémoire car nous n'avons pas à nous soucier du remplissage, des métadonnées d'objet pour la réflexion / répartition dynamique, et il sépare les champs chauds des champs froids (par exemple, nous ne sommes pas nécessairement concernés par les données des champs comme la couleur d'une particule pendant le passage physique, il serait donc inutile de la charger dans une ligne de cache pour ne pas l'utiliser et l'expulser).
Pour faciliter l'utilisation du code, il peut être utile d'écrire vos propres conteneurs redimensionnables de base qui stockent des tableaux de flottants, des tableaux d'entiers et des tableaux de booléens. Encore une fois, vous ne pouvez pas utiliser de génériques et
ArrayList
ici (au moins depuis la dernière fois que j'ai vérifié) car cela nécessite des objets gérés par GC, pas des données primitives contiguës. Nous voulons utiliser un tableau contiguint
, par exemple, des tableaux non gérés par GC deInteger
qui ne seront pas nécessairement contigus après avoir quitté l'espace Eden.Avec les tableaux de types primitifs, ils sont toujours garantis contigus, et vous obtenez donc la localité de référence extrêmement souhaitable (pour le traitement séquentiel des particules, cela fait toute une différence) et tous les avantages que le regroupement d'objets est censé fournir. Avec un tableau d'objets, il est plutôt quelque peu analogue à un tableau de pointeurs qui commencent par pointer vers les objets de manière contiguë en supposant que vous les avez tous alloués en même temps dans l'espace Eden, mais après un cycle GC, peut pointer partout dans le mettre en mémoire.
la source