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.
- Il est toujours utile dans n'importe quelle langue de regrouper des ressources complexes qui sont très coûteuses à détruire et à créer comme des threads. Celles-ci peuvent valoir la peine d'être regroupées, car les dépenses de création et de destruction n'ont pratiquement rien à voir avec la mémoire associée au descripteur d'objet de la ressource. Cependant, les particules ne rentrent pas dans cette catégorie.
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:
class Particle
{
public float x;
public float y;
public boolean alive;
}
Faites quelque chose comme:
class Particles
{
// X positions of all particles. Resize on demand using
// 'java.util.Arrays.copyOf'. We do not use an ArrayList
// since we want to work directly with contiguously arranged
// primitive types for optimal memory access patterns instead
// of objects managed by GC.
public float x[];
// Y positions of all particles.
public float y[];
// Alive/dead status of all particles.
public bool alive[];
}
Maintenant, pour réutiliser la mémoire pour les particules existantes, vous pouvez le faire:
class Particles
{
// X positions of all particles.
public float x[];
// Y positions of all particles.
public float y[];
// Alive/dead status of all particles.
public bool alive[];
// Next free position of all particles.
public int next_free[];
// Index to first free particle available to reclaim
// for insertion. A value of -1 means the list is empty.
public int first_free;
}
Maintenant, quand le nth
particule meurt, pour permettre sa réutilisation, poussez-la dans la liste gratuite comme suit:
alive[n] = false;
next_free[n] = first_free;
first_free = n;
Lors de l'ajout d'une nouvelle particule, voyez si vous pouvez faire apparaître un index dans la liste gratuite:
if (first_free != -1)
{
int index = first_free;
// Pop the particle from the free list.
first_free = next_free[first_free];
// Overwrite the particle data:
x[index] = px;
y[index] = py;
alive[index] = true;
next_free[index] = -1;
}
else
{
// If there are no particles in the free list
// to overwrite, add new particle data to the arrays,
// resizing them if needed.
}
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 contigu int
, 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.