Pour argumenter: après avoir lu jusqu'à la fin, vous comprendrez comment et pourquoi exactement GC fonctionne

Je dirai tout de suite: je n'attends jamais de réponse détaillée à cette question sur la sécurité sociale. C'est stupide et dans mon cas égoïste. Cependant, à mon avis, en plus de l'intérêt général pour la plateforme, il est très utile de savoir comment elle fonctionne, car cela supprime un certain nombre de problèmes. Par exemple, il exclut l'option lorsque le développeur estime que Dispose est appelé automatiquement et que vous n'avez pas besoin de l'appeler vous-même. Ou si le développeur est plus expérimenté, l'aide automatiquement, au niveau de la mémoire musculaire, à écrire du code qui conduit au moins de problèmes.


Une autre question que je n'aime pas vraiment subjectivement est de savoir comment son travail est expliqué. Par conséquent, je propose une approche alternative décrite dans mon livre, .NET Platform Architecture .


Si nous voulons bien comprendre pourquoi ces deux algorithmes de gestion de la mémoire ont été choisis: Sweep et Compact, nous devrons considérer des dizaines d'algorithmes de gestion de la mémoire qui existent dans le monde: en commençant par des dictionnaires ordinaires et en terminant par des structures sans verrouillage très complexes. Au lieu de cela, laissant nos pensées sur ce qui est utile, nous justifions simplement le choix et comprenons ainsi pourquoi le choix a été fait de cette façon. Nous ne regardons plus le livret de lancement du booster: nous avons entre nos mains un ensemble complet de documentation.


Le différend est mutuellement bénéfique: s'il n'est pas clair, je corrigerai les points peu clairs du livre , dont une petite partie est le texte donné.



J'ai choisi le format du raisonnement pour que vous vous sentiez comme les architectes de la plate-forme et moi-même sommes arrivés aux mêmes conclusions que les vrais architectes sont arrivés au siège de Microsoft à Redmond.

En fonction de la classification des objets alloués en fonction de leur taille, vous pouvez diviser l'espace d'allocation de mémoire en deux grandes sections: un endroit avec des objets de taille inférieure à un certain seuil et un endroit avec une taille supérieure à ce seuil et voyez quelle différence peut être faite dans la gestion de ces groupes (basée sur leur taille) et ce qui en découle.


Si nous considérons la gestion des « petits » objets conventionnels, nous pouvons voir que si nous adhérons à l'idée de stocker des informations sur chaque objet, il nous coûtera très cher de maintenir des structures de données de gestion de la mémoire qui stockeront les références à chacun de ces objets. En fin de compte, il peut s'avérer que pour stocker des informations sur un objet, vous aurez besoin d'autant de mémoire que prend l'objet lui-même. Au lieu de cela, vous devriez considérer: si pendant la collecte des ordures nous dansons à partir des racines, en pénétrant profondément dans le graphique à travers les champs sortants de l'objet, et si nous avons besoin d'un passage linéaire le long du tas uniquement pour identifier les objets poubelles, est-il nécessaire pour nous de stocker des informations sur chaque objet dans les algorithmes de gestion de la mémoire? La réponse est évidente: cela n'est pas nécessaire. Ainsi, nous pouvons essayer de partir du fait que nous ne devons pas stocker de telles informations: nous pouvons parcourir un tas de façon linéaire, en connaissant la taille de chaque objet et en déplaçant le pointeur à chaque fois de la taille de l'objet suivant.


Il n'y a pas de structures de données supplémentaires sur le tas qui contiennent des pointeurs vers chaque objet que le tas contrôle.

Cependant, quand nous n'avons plus besoin de mémoire, nous devons la libérer. Et lors de la libération de la mémoire, il devient difficile pour nous de compter sur le passage linéaire du tas: il est long et non efficace. En conséquence, nous arrivons à la conclusion que nous devons en quelque sorte stocker des informations sur les zones de mémoire libres.


Le tas contient des listes de mémoire libre.

Si, comme nous l'avons décidé, pour stocker des informations sur les zones libres, et tout en libérant de la mémoire, ces zones étaient trop petites, nous arrivons tout d'abord au même problème de stockage d'informations sur les zones libres que nous avons rencontré lors de l'examen des zones occupées (si sur les côtés de l'objet occupé a été libéré, afin de stocker des informations à son sujet, il est nécessaire dans le pire des cas 2/3 de sa taille. Pointeur + taille contre SyncBlockIndex + VMT + n'importe quel champ - dans le cas de l'objet). Cela semble à nouveau inutile, vous devez l'admettre: ce n'est pas toujours de la chance de libérer un groupe d'objets qui se succèdent. Habituellement, ils sont libérés de manière chaotique. Mais contrairement aux sites très fréquentés, que nous n'avons pas besoin de rechercher de manière linéaire, nous devons rechercher des sites gratuits, car lorsque nous allouons de la mémoire, nous pouvons en avoir besoin à nouveau. Par conséquent, un désir complètement naturel se pose de réduire la fragmentation et de comprimer le tas, déplaçant toutes les zones occupées vers des emplacements libres, formant ainsi une grande zone de la zone libre où la mémoire peut être allouée.


C'est de là que vient l'idée de l'algorithme de compactage.

Mais attendez, dites-vous. Après tout, cette opération peut être très difficile. Imaginez que vous ayez libéré un objet au tout début du tas. Et de quoi avez-vous besoin pour tout déplacer ?? Eh bien, bien sûr, vous pouvez imaginer le sujet des instructions vectorielles du CPU, que vous pouvez utiliser pour copier une énorme zone de mémoire occupée. Mais ce n'est que le début du travail. Nous devons également fixer tous les pointeurs des champs des objets aux objets qui ont subi des mouvements. Cette opération peut prendre énormément de temps. Non, nous devons partir d'autre chose. Par exemple, en divisant le segment entier de la mémoire du tas en secteurs et en travaillant séparément avec eux. Si nous travaillons séparément dans chaque secteur (pour la prévisibilité et la mise à l'échelle de cette prévisibilité - de préférence, des tailles fixes), l'idée de compression ne semble pas si lourde: il suffit de compresser un seul secteur et vous pouvez même commencer à parler du temps qu'il faut pour compresser un tel secteur .


Reste maintenant à comprendre sur quelle base se diviser en secteurs. Ici, nous devons nous tourner vers la deuxième classification, qui est introduite sur la plate-forme: le partage de mémoire, basé sur la durée de vie de ses éléments individuels.


La division est simple: si nous considérons que nous allouerons de la mémoire à mesure que les adresses augmentent, alors les premiers objets sélectionnés deviennent les plus anciens et ceux qui se trouvent dans les adresses seniors deviennent les plus jeunes. De plus, étant intelligent, vous pouvez conclure que dans les applications, les objets sont divisés en deux groupes: ceux qui ont été créés pour une longue durée de vie et ceux qui ont été créés pour vivre très peu. Par exemple, pour stocker temporairement des pointeurs vers d'autres objets sous la forme d'une collection. Ou les mêmes objets DTO. En conséquence, de temps en temps, en serrant un tas, nous obtenons un certain nombre d'objets à longue durée de vie - aux adresses inférieures et un certain nombre à courte durée de vie - chez les personnes âgées.


Ainsi, nous avons reçu des générations .

En divisant la mémoire en générations, nous avons l'occasion de regarder moins souvent les objets de l'ancienne génération, qui deviennent de plus en plus nombreux.


Mais une autre question se pose: si nous n'avons que deux générations, nous aurons des problèmes. Ou nous essaierons de faire fonctionner GC rapidement sans masque: alors la taille de la jeune génération, nous essaierons de faire la taille minimale. En conséquence, les objets échoueront accidentellement dans l'ancienne génération (si le GC fonctionnait "en ce moment, lors d'une furieuse allocation de mémoire pour de nombreux objets"). Ou, pour minimiser les pannes accidentelles, nous augmenterons la taille de la jeune génération. Ensuite, le GC sur la jeune génération fonctionnera assez longtemps, ralentissant et ralentissant l'application.


La sortie est l'introduction de la génération "moyenne". Adolescent. En d'autres termes, si vous vivez jusqu'à l'adolescence, vous êtes plus susceptible de vivre jusqu'à la vieillesse. L’essence de son introduction est de parvenir à un équilibre entre la génération la plus jeune et la génération la plus stable , où il est préférable de ne rien toucher. Il s'agit d'une zone où le sort des objets n'a pas encore été décidé. La première génération (n'oubliez pas ce que nous pensons de zéro) est également créée de petite taille et GC y apparaît moins souvent. La GC permet ainsi aux objets qui font partie de la première génération temporaire de ne pas entrer dans l'ancienne génération, ce qui est extrêmement difficile à collecter.


Nous avons donc eu l'idée de trois générations.

La prochaine couche d'optimisation est une tentative de refuser la compression. Après tout, si vous ne le faites pas, nous nous débarrassons d'une énorme couche de travail. Revenons à la question des sites gratuits.


Si après avoir utilisé toute la mémoire disponible dans le tas et que GC a été appelé, il y a un désir naturel de refuser la compression en faveur d'une allocation de mémoire supplémentaire à l'intérieur des sections libérées si leur taille est suffisante pour accueillir un certain nombre d'objets. Nous arrivons ici à l'idée d'un deuxième algorithme de libération de mémoire dans le GC, appelé Sweep : nous ne compressons pas la mémoire, nous utilisons des vides d'objets libérés pour placer de nouveaux objets


Nous avons donc décrit et justifié toutes les bases des algorithmes GC.

Ainsi, après deux jours, nous pouvons tirer quelques conclusions. Si je comprends bien, la plupart des gens comprennent la majeure partie du texte, voire l'ensemble. Certaines personnes ont répondu qu'elles ne comprenaient pas, certaines, respectivement, partiellement comprises. Le différend a été remporté par une équipe de lecteurs, mais avec une légère marge, comme on dit. Mais, comme je l'ai dit, tout le monde en bénéficiera: le texte sera amendé et complété. De plus, mis à jour aux deux endroits: à la fois dans le livre et ici, dans l'article.


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


All Articles