Pourquoi le tas d'objets volumineux et pourquoi nous en soucions-nous?

105

J'ai lu sur les générations et le tas d'objets volumineux. Mais je ne comprends toujours pas quelle est la signification (ou l'avantage) d'avoir un tas d'objets de grande taille?

Qu'est-ce qui aurait pu mal tourner (en termes de performances ou de mémoire) si CLR s'était simplement appuyé sur la génération 2 (étant donné que le seuil pour Gen0 et Gen1 est petit pour gérer les grands objets) pour stocker de gros objets?

Manish Basantani
la source
6
Cela me pose deux questions pour les concepteurs .NET: 1. Pourquoi une défragmentation LOH n'est-elle pas appelée avant qu'une OutOfMemoryException ne soit lancée? 2. Pourquoi les objets LOH n'ont-ils pas une affinité pour rester ensemble (les grands préfèrent la fin du tas et les petits au début)
Jacob Brewer

Réponses:

195

Un garbage collection ne se débarrasse pas seulement des objets non référencés, il compacte également le tas. C'est une optimisation très importante. Cela ne rend pas seulement l'utilisation de la mémoire plus efficace (pas de trous inutilisés), cela rend le cache du processeur beaucoup plus efficace. Le cache est un très gros problème sur les processeurs modernes, ils sont d'un ordre de grandeur facile plus rapides que le bus mémoire.

Le compactage se fait simplement en copiant des octets. Cela prend cependant du temps. Plus l'objet est grand, plus il est probable que le coût de sa copie l'emporte sur les améliorations possibles de l'utilisation du cache du processeur.

Ils ont donc exécuté une série de points de repère pour déterminer le seuil de rentabilité. Et arrivé à 85 000 octets comme point de coupure où la copie n'améliore plus les performances. Avec une exception spéciale pour les tableaux de double, ils sont considérés comme «grands» lorsque le tableau contient plus de 1000 éléments. C'est une autre optimisation pour le code 32 bits, l'allocateur de tas d'objets volumineux a la propriété spéciale d'allouer de la mémoire à des adresses alignées sur 8, contrairement à l'allocateur générationnel régulier qui alloue uniquement aligné sur 4. Cet alignement est un gros problème pour le double , lire ou écrire un double mal aligné coûte très cher. Curieusement, les informations Microsoft clairsemées ne mentionnent jamais de tableaux de longue durée, je ne sais pas ce qui se passe avec cela.

Fwiw, il y a beaucoup d'angoisse du programmeur à propos du gros tas d'objets qui ne soit pas compacté. Cela se déclenche invariablement lorsqu'ils écrivent des programmes qui consomment plus de la moitié de tout l'espace d'adressage disponible. Suivi en utilisant un outil comme un profileur de mémoire pour découvrir pourquoi le programme a bombardé même s'il y avait encore beaucoup de mémoire virtuelle inutilisée disponible. Un tel outil montre les trous dans la LOH, des blocs de mémoire inutilisés où auparavant un gros objet vivait mais était récupéré. Tel est le prix inévitable du LOH, le trou ne peut être réutilisé que par une allocation pour un objet de taille égale ou inférieure. Le vrai problème est de supposer qu'un programme devrait être autorisé à consommer toute la mémoire virtuelle à tout moment.

Un problème qui, autrement, disparaît complètement en exécutant simplement le code sur un système d'exploitation 64 bits. Un processus 64 bits a 8 téraoctets d'espace d'adressage de mémoire virtuelle disponibles, 3 ordres de grandeur de plus qu'un processus 32 bits. Vous ne pouvez tout simplement pas manquer de trous.

Pour faire court, la LOH rend le code plus efficace. Au détriment de l'utilisation de l'espace d'adressage de la mémoire virtuelle disponible moins efficace.


UPDATE, .NET 4.5.1 prend désormais en charge le compactage de la propriété LOH, GCSettings.LargeObjectHeapCompactionMode . Méfiez-vous des conséquences s'il vous plaît.

Hans Passant
la source
3
@ Hans Passant, pourriez-vous s'il vous plaît clarifier le système x64, vous voulez dire que ce problème disparaît complètement?
Johnny_D
Certains détails de mise en œuvre de la LOH ont du sens, mais certains me déroutent. Par exemple, je peux comprendre que si de nombreux objets volumineux sont créés et abandonnés, il peut généralement être souhaitable de les supprimer en masse dans une collection Gen2 plutôt qu'au coup par coup dans les collections Gen0, mais si l'on crée et abandonne par exemple un tableau de 22000 chaînes auquel aucune référence extérieure n'existe, quel avantage existe-t-il à avoir des collections Gen0 et Gen1 étiquetant toutes les 22 000 chaînes comme "en direct" sans se soucier de savoir s'il existe une référence au tableau?
supercat
6
Bien sûr, le problème de fragmentation est le même sur x64. Il ne faudra que quelques jours de plus pour exécuter le processus de votre serveur avant qu'il ne démarre.
Lothar
1
Hmm, non, ne sous-estimez jamais 3 ordres de grandeur. Combien de temps il faut pour ramasser les ordures sur un tas de 4 téraoctets est quelque chose que vous ne pouvez pas éviter de découvrir longtemps avant de s'en approcher.
Hans Passant
2
@HansPassant Pourriez-vous, s'il vous plaît, expliquer cette déclaration: "Combien de temps faut-il pour ramasser des ordures dans un tas de 4 téraoctets est quelque chose que vous ne pouvez pas éviter de découvrir longtemps avant de s'en approcher."
relativement_random
9

Si la taille de l'objet est supérieure à une valeur épinglée (85 000 octets dans .NET 1), CLR le place dans le tas d'objets volumineux. Cela optimise:

  1. Allocation d'objets (les petits objets ne sont pas mélangés avec de grands objets)
  2. Garbage collection (LOH collecté uniquement sur GC complet)
  3. Défragmentation de la mémoire (LOH n'est jamais rarement compactée)
oleksii
la source
9

La différence essentielle entre Small Object Heap (SOH) et Large Object Heap (LOH) est que la mémoire dans SOH est compactée lors de la collecte, alors que LOH non, comme cet article illustre . Le compactage d'objets volumineux coûte cher. Similaire aux exemples de l'article, disons que le déplacement d'un octet en mémoire nécessite 2 cycles, puis le compactage d'un objet de 8 Mo dans un ordinateur à 2 GHz nécessite 8 ms, ce qui représente un coût élevé. Étant donné que les objets volumineux (tableaux dans la plupart des cas) sont assez courants dans la pratique, je suppose que c'est la raison pour laquelle Microsoft épingle de gros objets dans la mémoire et propose LOH.

BTW, selon ce post , LOH ne génère généralement pas de problèmes de fragments de mémoire.

raisin
la source
1
Le chargement de grandes quantités de données dans des objets gérés éclipse généralement le coût de 8 ms pour compacter le LOH. En pratique, dans la plupart des applications Big Data, le coût LOH est insignifiant à côté du reste des performances de l'application.
Shiv
3

Le principe est qu'il est peu probable (et peut-être mal conçu) qu'un processus crée de nombreux objets volumineux de courte durée, de sorte que le CLR alloue des objets volumineux à un tas séparé sur lequel il exécute GC selon une planification différente de celle du tas normal. http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

Myles McDonnell
la source
Mettre également de gros objets sur, par exemple, la génération 2 pourrait finir par nuire aux performances, car il faudrait beaucoup de temps pour compacter la mémoire, surtout si une petite quantité était libérée et que des objets ÉNORMES devaient être copiés vers un nouvel emplacement. Le LOH actuel n'est pas compacté pour des raisons de performances.
Christopher Currens
Je pense que ce n'est qu'une mauvaise conception parce que le GC ne le gère pas bien.
CodesInChaos
@CodeInChaos Apparemment, il y a des améliorations à venir dans .NET 4.5
Christian.K
1
@CodeInChaos: Bien qu'il puisse être judicieux pour le système d'attendre une collection gen2 avant d'essayer de récupérer de la mémoire à partir d'objets LOH, même de courte durée, je ne vois aucun avantage en termes de performances à déclarer des objets LOH (et tous les objets auxquels ils contiennent références) vivent inconditionnellement pendant les collections gen0 et gen1. Y a-t-il des optimisations rendues possibles par une telle hypothèse?
supercat
@supercat J'ai regardé le lien mentionné par Myles McDonnell. Ma compréhension est la suivante: 1. La collecte LOH se produit dans un GC de génération 2. 2. La collection LOH n'inclut pas le compactage (au moment où l'article a été écrit). Au lieu de cela, il marquera les objets morts comme réutilisables et ces trous serviront les futures allocations de LOH s'ils sont suffisamment grands. En raison du point 1, étant donné qu'un GC de génération 2 serait lent s'il y a beaucoup d'objets dans la génération 2, je pense qu'il vaut mieux éviter d'utiliser LOH autant que possible dans ce cas.
robbie fan le
0

Je ne suis pas un expert du CLR, mais j'imagine que le fait d'avoir un tas dédié pour les gros objets peut empêcher des balayages GC inutiles des tas générationnels existants. L'allocation d'un objet volumineux nécessite une quantité importante de mémoire libre contiguë . Afin de fournir cela à partir des "trous" dispersés dans les tas générationnels, vous auriez besoin de compactions fréquentes (qui ne sont effectuées qu'avec des cycles GC).

Chris Shain
la source