web-dev-qa-db-fra.com

Comment implémenter un garbage collector?

Quelqu'un pourrait-il m'indiquer une bonne source sur la façon d'implémenter la collecte des ordures? Je fais un langage interprété de type LISP. Il utilise actuellement le comptage de références, mais bien sûr, il ne parvient pas à libérer des objets dépendants de façon circulaire.

J'ai lu la marque et le balayage, le marquage tricolore, le déplacement et l'immobilité, l'incrémentiel et le stop-the-world, mais ... je ne sais pas quelle est la meilleure façon de garder les objets soigneusement séparés en ensembles tout en conservant la per- la surcharge de mémoire de l'objet au minimum, ou comment faire les choses progressivement.

J'ai lu quelques langues avec le comptage de références qui utilisent la détection de référence circulaire, que je pourrais utiliser. Je suis conscient que je pourrais utiliser des collectionneurs disponibles gratuitement comme Boehm, mais j'aimerais apprendre à le faire moi-même.

J'apprécierais tout matériel en ligne avec une sorte de tutoriel ou d'aide pour les personnes sans expérience sur le sujet comme moi.

58
salvador p

Comme l'a suggéré delnan, j'ai commencé avec un algorithme de marquage et de balayage tricolore très naïf. J'ai réussi à garder les objets dans les ensembles en en faisant des nœuds de liste liée, mais cela ajoute beaucoup de données à chaque objet (le pointeur virtuel, deux pointeurs vers les nœuds, une énumération pour contenir la couleur). Cela fonctionne parfaitement, pas de mémoire perdue sur valgrind :) À partir d'ici, je pourrais essayer d'ajouter une liste gratuite pour le recyclage, ou une sorte de chose qui détecte quand il est pratique d'arrêter le monde, ou une approche incrémentielle, ou un allocateur spécial pour éviter la fragmentation ou autre chose. Si vous pouvez m'indiquer où trouver des informations ou des conseils (je ne sais pas si vous pouvez commenter une réponse à une question) sur la façon de faire ces choses ou quoi faire, je serais très reconnaissant. Je vérifierai le GC de Lua en attendant.

7
salvador p

Quelqu'un pourrait-il m'indiquer une bonne source sur la façon d'implémenter la collecte des ordures?

Il y a beaucoup de matériel avancé sur la collecte des ordures. Le Garbage Collection Handbook est génial. Mais j'ai trouvé qu'il y avait de précieuses informations de base, alors j'ai écrit quelques articles à ce sujet. Prototyping a mark-sweep garbage collector décrit un GC minimal mark-sweep écrit en F #. Le Garbage Collector très simultané décrit un collecteur simultané plus avancé. HLVM est une machine virtuelle que j'ai écrite qui comprend un collecteur stop-the-world qui gère le filetage.

La façon la plus simple d'implémenter un garbage collector est:

  1. Assurez-vous que vous pouvez rassembler les racines mondiales. Ce sont les variables locales et globales qui contiennent des références dans le tas. Pour les variables locales, poussez-les sur une pile fantôme pendant la durée de leur étendue.

  2. Assurez-vous que vous pouvez parcourir le tas, par exemple chaque valeur du tas est un objet qui implémente une méthode Visit qui renvoie toutes les références de cet objet.

  3. Conservez l'ensemble de toutes les valeurs allouées.

  4. Allouez en appelant malloc et en insérant le pointeur dans l'ensemble de toutes les valeurs allouées.

  5. Lorsque la taille totale de toutes les valeurs allouées dépasse un quota, démarrez la marque, puis balayez les phases. Cela parcourt récursivement le tas en accumulant l'ensemble de toutes les valeurs accessibles.

  6. La différence définie des valeurs allouées moins les valeurs accessibles est l'ensemble des valeurs inaccessibles. Parcourez-les en appelant free et en les supprimant de l'ensemble des valeurs allouées.

  7. Définissez le quota sur deux fois la taille totale de toutes les valeurs allouées.

29
Jon Harrop

Consultez la page suivante. Il a de nombreux liens. http://lua-users.org/wiki/GarbageCollection

8
Mike

J'ai implémenté un collecteur de déchets de copie de style Cheney en C dans environ 400 SLOC. Je l'ai fait pour un langage typé et, à ma grande surprise, la partie la plus difficile était en fait de communiquer les informations sur les choses qui sont des pointeurs et celles qui ne le sont pas. Dans un langage typé dynamiquement, cela est probablement plus facile car vous devez déjà utiliser une certaine forme de schéma de balisage.

Il existe également une nouvelle version du livre standard sur la collecte des ordures: "Le manuel de collecte des ordures: L'art de la gestion automatique de la mémoire" par Jones, Hosking, Moss . (Le site Amazon UK indique le 19 août 2011.)

4
nominolo

Une chose que je n'ai pas encore vue mentionnée est l'utilisation de poignées de mémoire. On peut éviter d'avoir à doubler la mémoire (comme ce serait le cas avec l'algorithme de copie de style Cheney) si chaque référence d'objet est un pointeur vers une structure qui contient l'adresse réelle de l'objet en question. L'utilisation de poignées pour les objets de mémoire rendra certaines routines un peu plus lentes (il faut relire l'adresse mémoire d'un objet à chaque fois qu'un événement s'est produit qui le déplacerait) mais pour les systèmes à un seul thread où la récupération de place ne se fera qu'à des moments prévisibles, cette ne pose pas trop de problème et ne nécessite pas de prise en charge spéciale du compilateur (les systèmes GC multithread nécessiteront probablement des métadonnées générées par le compilateur, qu'ils utilisent des descripteurs ou des pointeurs directs).

Si l'on utilise des poignées et utilise une liste liée pour les poignées actives (le même stockage peut être utilisé pour contenir une liste chaînée pour les poignées mortes nécessitant une réaffectation), on peut, après avoir marqué la fiche principale de chaque poignée, parcourir la liste des poignées , dans l'ordre d'allocation, et copiez le bloc auquel ce descripteur fait référence au début du segment. Étant donné que les poignées seront copiées dans l'ordre, il ne sera pas nécessaire d'utiliser une deuxième zone de tas. En outre, les générations peuvent être prises en charge en gardant une trace de certains pointeurs haut de gamme. Lors de la compactification de la mémoire, commencez par simplement compacter les éléments ajoutés depuis le dernier GC. Si cela ne libère pas suffisamment d'espace, compactez les éléments ajoutés depuis le dernier GC de niveau 1. Si cela ne libère pas assez d'espace, compactez tout. La phase de marquage devrait probablement agir sur des objets de toutes les générations, mais pas l'étape de compactage coûteuse.

En fait, en utilisant une approche basée sur les poignées, si l'on marque des choses de toutes les générations, on pourrait si on le souhaite calculer sur chaque GC passer la quantité d'espace qui pourrait être libérée à chaque génération. Si la moitié des objets dans Gen2 sont morts, il peut être utile de faire une collection Gen2 afin de réduire la fréquence des collections Gen1.

4
supercat

Implémentation de la récupération de place dans LISP

Immeuble LISP | http://www.lwh.jp/LISP/

Arcadia | https://github.com/kimtg/arcadia

3
KIM Taegyoon
2
cprogrammer

Je fais un travail similaire pour mon interprète postscript. plus d'informations via ma question. Je suis d'accord avec le commentaire de Delnan selon lequel un simple algorithme de balayage de marque est un bon point de départ. Vous aurez besoin de fonctions pour définir, cocher, effacer et itérer tous vos conteneurs. Une optimisation simple consiste à effacer la marque à chaque attribution d'un nouvel objet et à effacer la marque pendant le balayage; sinon, vous aurez besoin d'une passe complète pour effacer les marques avant de commencer à les définir.

0
luser droog