9
votes

GC.Collect sur la seule génération 2 et le grand heap objet

Dans mon application, il existe une heure spécifique lorsqu'un certain nombre de grands objets sont tous libérés à la fois. À ce moment-là, j'aimerais faire une collection d'ordures sur spécifiquement le grand heap objet (LOH).

Je suis conscient que vous ne pouvez pas faire cela, vous devez appeler gc.collect (2) car le GC n'est invoqué que sur le LOH lorsqu'il effectue une collection de génération 2. Cependant, j'ai lu dans la documentation qui appelant gc.collect (2) exécuterait toujours un GC sur les générations 1 et 0.

est-il possible de forcer le GC à seul collecter GEN 2, et non inclure GEN 1 ou GEN 0?

Si ce n'est pas possible, est-il une raison pour que le GC soit conçu de cette façon?


2 commentaires

Pourquoi voudriez-vous faire cela, c'est-à-dire ne pas collecter de GEN 0 ou 1? Le .NET GC fonctionne mieux quand il est laissé à ses propres appareils.


Je suis au courant de ça. Fondamentalement, vous ne voulez jamais forcer manuellement un GC, car ils sont des opérations intensives. Depuis que c'est le cas, lorsque je vois la nécessité de gérer un GC, je voudrais que cela ne fonctionne que contre la génération spécifique, plutôt que de faire un GC complet. J'essaie d'être plus particulier de mon utilisation du GC, et cela ne me laisse pas.


4 Réponses :


14
votes

Ce n'est pas possible. Le GC est conçu de sorte qu'une collection de génération 2 collecte toujours la génération 0 et 1.

edit : Vous a trouvé une source pour cela sur Blog de GC Developer :

GEN2 GC nécessite une collection complète (Gen0, Gen1, Gen2 et Loh! Grand Les objets sont GC'ed à chaque GEN2 GC Même lorsque le GC n'a pas été déclenché par manque d'espace dans loh. Notez qu'il y a n'est pas un gc qui ne collecte que objets.) qui prend beaucoup plus de temps que collections de génération plus jeunes.

EDIT 2 : du même blog utilise GC efficacement Partie 1 et Partie 2 < / a> apparemment collections de GEN0 et de GEN1 sont rapides par rapport à une collection GEN2, de sorte qu'il me semble raisonnable que ce ne soit que le GEN2 ne serait pas de beaucoup de prestations de performance. Il pourrait y avoir une raison plus fondamentale, mais je ne suis pas sûr. Peut-être que la réponse est dans certains articles sur ce blog.


1 commentaires

Merci! Veuillez noter que j'ai modifié ma question pour que cela vous demande également pourquoi il est contraint de cette manière.



6
votes

Étant donné que toutes les nouvelles allocations (autres que pour les gros objets) Toujours vont dans Gen0, le GC est conçu pour toujours collecter à partir de la génération spécifiée et ci-dessous. Lorsque vous appelez gc.collect (2) , vous disez au GC de collecter de GEN0, GEN1 et GEN2.

Si vous êtes certain que vous traitez avec beaucoup d'objets importants (objets qu'à la date d'allocation sont suffisamment importants pour être placés sur le LOH), la meilleure option est de vous assurer que vous les définissez sur NULL (rien dans VB) lorsque Vous avez fini avec eux. L'allocation de LOH tente d'être intelligent et de réutiliser des blocs. Par exemple, si vous avez alloué un objet de 1 Mo sur le LOH, puis je l'ai éliminé et définissez-le sur NULL, vous seriez laissé avec un "trou" de 1 Mo. La prochaine fois que vous allouez quelque chose sur le loh qui est de 1mb ou plus petit de taille, il remplira ce trou (et continuera à le remplir jusqu'à ce que la prochaine allocation soit trop grande pour s'adapter à l'espace restant, à quel point il allouera un nouveau bloc.)

Gardez à l'esprit que les générations de .NET ne sont pas des choses physiques, mais sont des séparations logiques pour augmenter les performances de GC. Étant donné que toutes les nouvelles allocations sont entrées dans Gen0, c'est toujours la première génération à être collectée. Chaque cycle de collecte qui fonctionne, tout dans une génération inférieure qui survit à la collection est "promu" à la prochaine génération la plus élevée (jusqu'à ce que dans la GEN2).

Dans la plupart des cas, le GC n'a pas besoin d'aller au-delà de la collecte de gen0. La mise en œuvre actuelle du GC est capable de collecter GEN0 et GEN1 en même temps, mais il ne peut pas collecter GEN2 pendant la collecte de GEN0 ou de GEN1. (.NET 4.0 Détende cette contrainte une bonne affaire et pour la plupart, le GC est capable de collecter GEN2 pendant que Gen0 ou Gen1 sont également collectés.)


3 commentaires

Votre explication est un excellent aperçu de la façon dont le GC fonctionne, mais cela ne clarifie pas pourquoi il y a une contrainte qui exclut une collection strictement GEN 1 ou GEN 2.


Réglage myvar = null n'apporte rien. Voir le bas de Bryancook.net/2008/05/ Net-Garge-Collection-Behavior-for.ht ml


Je crois que la raison est que la promotion d'objets à des générations supérieures n'est évaluée que (et effectuée) pendant la GC, alors peut-être qu'il n'y a peut-être rien (nouveau) à faire si vous ne récupérez pas les générations inférieures ...



0
votes

Pour répondre à la question "Pourquoi": Physiquement, il n'existe pas de Gen0 et de Gen1 ou Gen2. Ils utilisent tous les mêmes blocs de mémoire dans l'espace d'adresses virtuel. La distinction entre eux n'est vraiment faite que pratiquement en se déplaçant autour d'une limite de frontière imaginaire.

Chaque objet (petit) est attribué à partir de la zone de tas de gen0. Si - après une collection - il survit, il est déplacé "vers le bas" dans cette zone du bloc de tas de géré, qui a finalement été libéré des ordures. Ceci est fait en compactant le tas. Après la finition de la collection complète, la nouvelle "bordure" pour Gen1 est définie sur l'espace juste après ces objets survivants.

Donc, si vous voulez essayer de nettoyer GEN0 et / ou GEN1, vous ouvrez des trous dans le tas qui doivent être fermés en compactant le tas «complet» - même des objets de Gen0. De toute évidence, cela ne ferait aucun sens, car la plupart de ces objets seraient des ordures de toute façon. Il ne sert à rien de les déplacer. Et aucun point de créer et de laisser de gros trous sur le tas (autrement compactant).


2 commentaires

Conceptuellement, il est plus simple de penser aux trucs de Gen2 comme étant au fond du tas, avec Gen1 immédiatement sur le dessus et Gen0 au sommet du haut. Les objets plus anciens sont toujours en dessous des objets plus jeunes. Lorsque le compactage se produit, le système passe par tous les objets en direct dans la ou les générations à compacter, en commençant par les objets les plus anciens et déplace chaque objet au point disponible le plus bas. Le marqueur "Haut de Gen2" est situé juste au-dessus de l'objet de Gen1 nouvellement copié le plus haut (maintenant dans GEN2). De même, le marqueur "Haut du Gen1" est situé juste au-dessus de l'objet de Gen0 nouvellement copié le plus haut.


L'objectif de tout cela est de libérer l'espace disponible contigu au-dessus de GEN0. Une grande partie de la raison du compactage du GEN2 est d'autoriser les objets GEN1 à descendre; De même Compactage Gen1 permet aux objets Gen0 de descendre. Il y a des astuces étranges .NET utilise des utilisations pour accélérer les déterminations de savoir si des objets sont "en direct"; En pratique, ils veulent dire que si un objet Gen2 détient une référence à un objet GEN0, l'objet Gen0 ne sera pas à collectionner jusqu'à ce que la référence soit détruite ou que l'objet GEN2 puisse être collecté.



0
votes

Chaque fois que le système effectue une collection de déchets d'une génération particulière, il doit examiner chaque objet qui pourrait contenir une référence à n'importe quel objet de cette génération. Dans de nombreux cas, les anciens objets ne feront que des références à d'autres objets anciens; Si le système effectue une collection GEN0, il peut ignorer les objets qui ne tiennent que des références à ceux de GEN1 et / ou de GEN2. De même, s'il s'agit d'une collection GEN1, il peut ignorer tous les objets qui ne tiennent que des références à GEN2. Étant donné que l'examen et le marquage des objets représentent une grande partie du temps requis pour la collecte des ordures, la possibilité de sauter des objets plus anciens représente entièrement une économie de temps considérable.

Incidemment, si vous vous demandez comment le système "sait" si un objet peut contenir des références à des objets plus récents, le système dispose de code spécial pour définir quelques bits dans le descripteur de chaque objet si l'objet est écrit. Le premier bit est réinitialisé à chaque collection de déchets et s'il est toujours réinitialisé à la prochaine collecte des ordures, le système saura qu'il ne peut contenir aucune référence à des objets Gen0 (puisque tous les objets existants lorsque l'objet a été écrit et n'étiendait pas effacé par la collection précédente sera GEN1 ou GEN2). Le second bit est réinitialisé à chaque collection de déchets GEN1 et s'il est toujours réinitialisé à la prochaine collection de déchets GEN1, le système saura qu'il ne peut contenir aucune référence aux objets GEN0 ou GEN1 (tous les objets auxquels il contient des références sont maintenant GEN2) . Notez que le système ne sait pas ou ne se souciait pas si les informations qui ont été écrites sur un objet comprenaient une référence GEN0 ou GEN1. Le piège requis lors de la rédaction d'un objet non étiqueté coûte cher et empêcherait grandement les performances si elle devait être traitée à chaque fois qu'un objet est écrit. Pour éviter cela, les objets sont étiquetés chaque fois que ecrire se produit, de sorte que tout écrit supplémentaire avant la prochaine collecte des ordures puisse procéder sans interruption.


0 commentaires