Optimisation des programmes pour Garbage Collector

Il n'y a pas si longtemps, un excellent article est paru sur Habré Optimization of garbage collection dans un service .NET très chargé . Cet article est très intéressant dans la mesure où les auteurs, armés de théorie, ont fait l'impossible plus tôt: ils ont optimisé leur application en utilisant la connaissance du GC. Et si auparavant nous n'avions aucune idée du fonctionnement de ce GC, il nous est maintenant présenté sur un plateau d'argent grâce aux efforts de Konrad Cocos dans son livre Pro .NET Memory Management . Quelles conclusions ai-je tirées pour moi? Faisons une liste des problèmes et réfléchissons à la façon de les résoudre.


Lors du récent atelier CLRium # 5: Garbage Collector, nous avons parlé de GC toute la journée. Cependant, j'ai décidé de publier un rapport avec décodage de texte. Ceci est un discours sur les conclusions concernant l'optimisation des applications.



Réduisez la connectivité entre les générations


Le problème


Pour optimiser la vitesse de collecte des ordures, le GC collecte la plus jeune génération chaque fois que possible. Mais pour ce faire, il a également besoin d'informations sur les liens des générations plus anciennes (elles agissent dans ce cas comme une racine supplémentaire): de la table à cartes.


Dans le même temps, un lien entre la génération plus âgée et la génération plus jeune vous oblige à couvrir la zone avec une table à cartes:


  • 4 octets se chevauchent 4 ko ou max. 320 objets - pour l'architecture x86
  • 8 octets se chevauchent 8 Ko ou max. 320 objets - pour l'architecture x64

C'est-à-dire GC, vérifiant la table des cartes, rencontrant une valeur non nulle, est obligé de vérifier un maximum de 320 objets pour la présence de liens sortants dans notre génération.


Par conséquent, les liens clairsemés dans la jeune génération rendront le GC plus long


Solution


  • Localisez les objets avec des connexions dans la jeune gĂ©nĂ©ration - Ă  proximitĂ©;
  • Si le trafic d'objets de gĂ©nĂ©ration zĂ©ro est supposĂ©, utilisez la traction. C'est-Ă -dire faire un pool d'objets (il n'y en aura pas de nouveaux: il n'y aura pas d'objets de gĂ©nĂ©ration zĂ©ro). Et en outre, en «rĂ©chauffant» le pool avec deux GC consĂ©cutifs afin que son contenu soit garanti en Ă©chec dans la deuxième gĂ©nĂ©ration, vous Ă©vitez ainsi les liens avec la jeune gĂ©nĂ©ration et avez des zĂ©ros dans le tableau des cartes;
  • Évitez les liens avec la jeune gĂ©nĂ©ration;

Évitez une forte connectivité


Le problème


Comme il ressort des algorithmes de la phase de compression des objets en SOH:


  • Pour compresser le tas, vous devez faire le tour de l'arborescence et vĂ©rifier tous les liens, en les corrigeant pour de nouvelles valeurs
  • De plus, les liens de la table de cartes affectent des groupes entiers d'objets

Par conséquent, la forte connectivité générale des objets peut conduire à un affaissement pendant la GC.


Solution


  • Avoir des objets fortement connectĂ©s Ă  proximitĂ©, en une gĂ©nĂ©ration
  • Évitez les liens inutiles en gĂ©nĂ©ral (par exemple, au lieu de dupliquer les liens this-> handle, utilisez le dĂ©jĂ  existant this-> Service-> handle)
  • Évitez le code avec une connectivitĂ© cachĂ©e. Par exemple, les fermetures

Surveiller l'utilisation des segments


Le problème


Lors de travaux intensifs, une situation peut survenir lorsque l'allocation de nouveaux objets entraîne des retards: l'allocation de nouveaux segments sous le tas et leur dégagement supplémentaire lors du nettoyage des ordures


Solution


  • Utilisation de PerfMon / Sysinternal Utilities pour contrĂ´ler les points de sĂ©lection des nouveaux segments et leur dĂ©gagement et libĂ©ration
  • Si nous parlons de LOH, qui est un trafic de tampon dense, utilisez ArrayPool
  • Si nous parlons de SOH, assurez-vous que les objets de la mĂŞme durĂ©e de vie sont mis en Ă©vidence Ă  proximitĂ©, fournissant un balayage au lieu de collecter
  • SOH: utiliser des pools d'objets

Ne pas allouer de mémoire dans les sections de code chargées


Le problème


La section de code chargée alloue de la mémoire:


  • Par consĂ©quent, GC sĂ©lectionne une fenĂŞtre d'allocation non pas de 1 Ko, mais de 8 Ko.
  • Si la fenĂŞtre manque d'espace, cela conduit Ă  un GC et Ă  une expansion de la zone fermĂ©e
  • Un flux dense de nouveaux objets rendra rapidement les objets de courte durĂ©e d'autres threads Ă  l'ancienne gĂ©nĂ©ration avec des conditions de collecte des ordures moins bonnes
  • Ce qui augmentera le temps de collecte des ordures
  • Ce qui mènera Ă  arrĂŞter le monde plus longtemps mĂŞme en mode simultanĂ©

Solution


  • Interdiction complète de l'utilisation des fermetures dans les sections critiques du code
  • Interdiction complète de la boxe sur les sections critiques du code (vous pouvez utiliser l'Ă©mulation en tirant si nĂ©cessaire)
  • Lorsqu'il est nĂ©cessaire de crĂ©er un objet temporaire pour le stockage de donnĂ©es, utilisez des structures. Mieux vaut ref struct. Lorsque le nombre de champs est supĂ©rieur Ă  2, transmettre par ref

Évitez les allocations de mémoire inutiles dans LOH


Le problème


Placer des tableaux dans LOH conduit à la fragmentation ou à la pondération de la procédure GC


Solution


  • Utilisez la division des tableaux en sous-tableaux et une classe qui encapsule la logique de travail avec de tels tableaux (c'est-Ă -dire, au lieu de List <T>, oĂą le mĂ©ga-tableau est stockĂ©, sa liste avec le tableau [] [], divisant le tableau un peu plus court)
    • Les tableaux iront Ă  SOH
    • Après quelques collectes d'ordures, ils se coucheront Ă  cĂ´tĂ© d'objets toujours vivants et cesseront d'influencer la collecte des ordures
  • ContrĂ´lez l'utilisation de tableaux doubles avec une longueur de plus de 1000 Ă©lĂ©ments.

Lorsque cela est justifié et possible, utilisez la pile de threads


Le problème


Il existe un certain nombre d'objets ultra-courts ou d'objets vivant dans un appel de méthode (y compris les appels internes). Ils créent du trafic d'objets


Solution


  • Utilisation de l'allocation de mĂ©moire sur la pile dans la mesure du possible:
    • Il ne charge pas un tas
    • Ne charge pas GC
    • LibĂ©ration de mĂ©moire - InstantanĂ©e
  • Utilisez Span T x = stackalloc T[]; au lieu de new T[] si possible
  • Utilisez Span/Memory si possible
  • Convertir des algorithmes en types de ref stack (StackList: struct, ValueStringBuilder )

Objets gratuits le plus tĂ´t possible


Le problème


Conçus comme éphémères, les objets tombent dans gen1, et parfois dans gen2.
Il en résulte un GC plus lourd qui dure plus longtemps


Solution


  • Vous devez libĂ©rer la rĂ©fĂ©rence d'objet le plus tĂ´t possible
  • Si un long algorithme contient du code qui fonctionne avec n'importe quel objet, il est espacĂ© par le code. Mais qui peuvent ĂŞtre regroupĂ©s en un seul endroit, il est nĂ©cessaire de le regrouper, permettant ainsi de les collecter plus tĂ´t.
    • Par exemple, Ă  la ligne 10, la collecte a Ă©tĂ© supprimĂ©e et Ă  la ligne 120, elle a Ă©tĂ© filtrĂ©e.

Pas besoin d'appeler GC.Collect ()


Le problème


Il semble souvent que si vous appelez GC.Collect (), cela corrigera la situation


Solution


  • Il est beaucoup plus correct d'apprendre les algorithmes de fonctionnement du GC, de regarder l'application sous ETW et d'autres outils de diagnostic (JetBrains dotMemory, ...)
  • Optimiser les zones les plus problĂ©matiques

Évitez d'épingler


Le problème


L'épinglage pose un certain nombre de problèmes:


  • Complique la collecte des ordures
  • CrĂ©e des espaces mĂ©moire libres (noeuds Ă©lĂ©ments de liste libre, table de briques, compartiments)
  • Peut laisser certains objets dans la jeune gĂ©nĂ©ration, tout en formant des liens Ă  partir de la table Ă  cartes

Solution


S'il n'y a pas d'autre issue, utilisez fixed () {}. Cette méthode de commit ne fait pas de vrai commit: elle ne se produit que lorsque le GC a fonctionné entre accolades.


Évitez la finalisation


Le problème


La finalisation n'est pas appelée de façon déterministe:


  • Uninvited Dispose () entraĂ®ne la finalisation de tous les liens sortants de l'objet
  • Les objets dĂ©pendants sont retardĂ©s plus longtemps que prĂ©vu
  • Vieillissement, passage aux gĂ©nĂ©rations plus âgĂ©es
  • Si en mĂŞme temps ils contiennent des liens vers des plus jeunes, ils gĂ©nèrent des liens depuis la table des cartes
  • Compliquer l'assemblage des gĂ©nĂ©rations plus anciennes, les fragmenter et conduire au compactage au lieu du balayage

Solution


Appelez doucement Dispose ()


Évitez trop de threads


Le problème


Avec un grand nombre de threads, le contexte d'allocation se développe, ils sont alloués à chaque thread:


  • En consĂ©quence, GC.Collect arrive plus rapidement.
  • En raison du manque d'espace dans le segment Ă©phĂ©mère, Collect suivra le balayage collectif

Solution


  • ContrĂ´lez le nombre de threads par le nombre de cĹ“urs

Évitez le trafic d'objets de différentes tailles


Le problème


Lors du trafic d'objets de tailles et de durées de vie différentes, une fragmentation se produit:


  • Augmenter le taux de fragmentation
  • DĂ©clenchement de collection avec une phase de changement d'adresse dans tous les objets de rĂ©fĂ©rencement

Solution


Si le trafic d'objets est supposé:


  • VĂ©rifiez la prĂ©sence de champs supplĂ©mentaires, approximant la taille
  • VĂ©rifiez l'absence de manipulation des chaĂ®nes: si possible, remplacez par ReadOnlySpan / ReadOnlyMemory
  • Relâchez le lien dès que possible
  • Profitez de la traction
  • Chauffez les caches et les piscines avec un double GC pour compacter les objets. Ainsi, vous Ă©vitez les problèmes avec la table Ă  cartes.

Source: https://habr.com/ru/post/fr453082/


All Articles