En rÚgle générale, vous n'avez pas à vous soucier du garbage collector et de l'utilisation de la mémoire lorsque vous écrivez du code Python. DÚs que les objets ne sont plus nécessaires, Python libÚre automatiquement la mémoire d'eux. Malgré cela, comprendre comment GC fonctionne vous aidera à écrire un meilleur code.
Gestionnaire de mémoire
Contrairement à d'autres langages populaires, Python ne libÚre pas toute la mémoire sur le systÚme d'exploitation dÚs qu'il supprime un objet. Au lieu de cela, il utilise un gestionnaire de mémoire supplémentaire conçu pour les petits objets (dont la taille est inférieure à 512 octets). Pour travailler avec de tels objets, il alloue de grands blocs de mémoire, dans lesquels de nombreux petits objets seront stockés à l'avenir.
DĂšs qu'un des petits objets est supprimĂ© - la mĂ©moire du dessous ne va pas au systĂšme d'exploitation, Python le laisse pour de nouveaux objets de mĂȘme taille. S'il ne reste aucun objet dans l'un des blocs de mĂ©moire allouĂ©s, Python peut le libĂ©rer sur le systĂšme d'exploitation. En rĂšgle gĂ©nĂ©rale, les blocs sont libĂ©rĂ©s lorsque le script crĂ©e de nombreux objets temporaires.
Ainsi, si un processus Python de longue durée commence à consommer plus de mémoire au fil du temps, cela ne signifie pas du tout que votre code a un problÚme de fuite de mémoire. Si vous voulez en savoir plus sur le gestionnaire de mémoire en Python, vous pouvez le lire dans
mon autre article .
Algorithmes de collecte des ordures
L'interpréteur python standard (CPython) utilise deux algorithmes à la fois, le comptage des références et le récupérateur de place générationnel (ci-aprÚs GC), mieux connu sous le nom de
module gc standard de Python.
L'algorithme de comptage de liens est trÚs simple et efficace, mais il présente un gros inconvénient. Il ne sait pas définir les références circulaires. C'est pour cette raison qu'en python, il existe un collecteur supplémentaire appelé GC générationnel qui surveille les objets avec des références circulaires potentielles.
En Python, l'algorithme de comptage de rĂ©fĂ©rence est fondamental et ne peut pas ĂȘtre dĂ©sactivĂ©, tandis que le GC est facultatif et peut ĂȘtre dĂ©sactivĂ©.
Algorithme de comptage de liens
L'algorithme de comptage de liens est l'une des techniques de collecte de déchets les plus simples. Les objets sont supprimés dÚs qu'ils ne sont plus référencés.
En Python, les variables ne stockent pas de valeurs, mais agissent comme des références aux objets. C'est-à -dire que lorsque vous affectez une valeur à une nouvelle variable, d'abord un objet avec cette valeur est créé, et alors seulement la variable commence à s'y référer. Plusieurs variables peuvent référencer un seul objet.
Chaque objet en Python contient un champ supplémentaire (compteur de référence), qui stocke le nombre de liens vers celui-ci. DÚs que quelqu'un fait référence à un objet, ce champ est incrémenté de un. Si, pour une raison quelconque, le lien disparaßt, ce champ est réduit de un.
Exemples lorsque le nombre de liens augmente:
- opérateur d'affectation
- passer des arguments
- insérer un nouvel objet dans la feuille (le nombre de liens pour l'objet augmente)
- une construction de la forme foo = bar (foo commence Ă se rĂ©fĂ©rer au mĂȘme objet que bar)
DÚs que le compteur de référence pour un objet spécifique atteint zéro, l'interpréteur démarre le processus de destruction de l'objet. Si l'objet distant contient des liens vers d'autres objets, ces liens sont également supprimés. Ainsi, la suppression d'un objet peut entraßner la suppression d'autres.
Par exemple, si une liste est supprimée, le nombre de références dans tous ses éléments est réduit de un. Si tous les objets de la liste ne sont utilisés nulle part ailleurs, ils seront également supprimés.
Les variables déclarées en dehors des fonctions, classes et blocs sont appelées globales. En rÚgle générale, le cycle de vie de ces variables est égal à la durée de vie du processus Python. Ainsi, le nombre de références à des objets référencés par des variables globales ne tombe jamais à zéro.
Les variables déclarées à l'intérieur d'un bloc (fonction, classe) ont une visibilité locale (c'est-à -dire qu'elles ne sont visibles qu'à l'intérieur du bloc). DÚs que l'interpréteur python quitte le bloc, il détruit tous les liens créés par les variables locales à l'intérieur.
Vous pouvez toujours vérifier le nombre de liens à l'aide de la fonction
sys.getrefcount
.
Exemple de compteur de liens:
foo = []
La principale raison pour laquelle l'interprĂ©teur standard (CPython) utilise un compteur de rĂ©fĂ©rence est historique. Il y a actuellement beaucoup de dĂ©bats sur cette approche. Certaines personnes pensent qu'un ramasse-miettes peut ĂȘtre beaucoup plus efficace sans algorithme de comptage de liens. Cet algorithme a de nombreux problĂšmes, tels que des liens circulaires, des threads de blocage, ainsi que des frais supplĂ©mentaires pour la mĂ©moire et le processeur.
Le principal avantage de cet algorithme est que les objets sont supprimés immédiatement dÚs qu'ils ne sont pas nécessaires.
Poubelle en option
Pourquoi avons-nous besoin d'un algorithme supplémentaire alors que nous avons déjà un comptage de références?
Malheureusement, l'algorithme de comptage de liens classique a un gros inconvénient - il ne sait pas comment trouver des liens circulaires. Les bouclages se produisent lorsqu'un ou plusieurs objets se référencent.
Deux exemples:

Comme vous pouvez le voir, le
lst
objet se rĂ©fĂšre Ă lui-mĂȘme, tandis que
object1
et
object1
réfÚrent l'un à l'autre. Pour de tels objets, le nombre de références sera toujours 1.
Démo Python:
import gc
Dans l'exemple ci-dessus, l'instruction del supprime les rĂ©fĂ©rences Ă nos objets (pas aux objets eux-mĂȘmes). Une fois que Python a exĂ©cutĂ© une instruction del, ces objets deviennent inaccessibles Ă partir du code Python. Cependant, avec le module gc dĂ©sactivĂ©, ils resteront toujours en mĂ©moire, car ils avaient des rĂ©fĂ©rences circulaires et leur compteur est toujours un. Vous pouvez explorer visuellement de telles relations en utilisant la bibliothĂšque
objgraph
.
Afin de résoudre ce problÚme, un algorithme supplémentaire appelé
module gc a été ajouté en Python 1.5. La seule tùche dont est la suppression des objets cycliques auxquels le code n'a plus accÚs.
Les bouclages ne peuvent se produire que dans des objets «conteneurs». C'est-à -dire dans des objets qui peuvent stocker d'autres objets, tels que des listes, des dictionnaires, des classes et des tuples. GC ne garde pas trace des types simples et immuables, à l'exception des tuples. Certains tuples et dictionnaires sont également exclus de la liste de suivi lorsque certaines conditions sont remplies. Avec tous les autres objets, l'algorithme de comptage de référence est garanti pour faire face.
Lorsque le GC est déclenché
Contrairement à l'algorithme de comptage de référence, le GC cyclique ne fonctionne pas en temps réel et s'exécute périodiquement. Chaque exécution du collecteur crée des micro-pauses dans le code, donc CPython (l'interpréteur standard) utilise diverses heuristiques pour déterminer la fréquence du garbage collector.
Le ramasse-miettes cyclique divise tous les objets en 3 gĂ©nĂ©rations (gĂ©nĂ©rations). De nouveaux objets entrent dans la premiĂšre gĂ©nĂ©ration. Si la nouvelle installation survit au processus de collecte des ordures, elle passe Ă la gĂ©nĂ©ration suivante. Plus la gĂ©nĂ©ration est Ă©levĂ©e, moins il est scannĂ© pour les dĂ©chets. Ătant donnĂ© que les nouveaux objets ont souvent une durĂ©e de vie trĂšs courte (sont temporaires), il est logique de les interroger plus souvent que ceux qui ont dĂ©jĂ traversĂ© plusieurs Ă©tapes de la collecte des ordures.
Chaque génération a un compteur spécial et un seuil de réponse, lorsque le processus de collecte des ordures est déclenché. Chaque compteur stocke le nombre d'allocations moins le nombre de désallocations dans une génération donnée. DÚs qu'un objet conteneur est créé en Python, il vérifie ces compteurs. Si les conditions fonctionnent, le processus de récupération de place commence.
Si plusieurs générations ou plus ont franchi le seuil à la fois, la génération la plus ùgée est sélectionnée. Cela est dû au fait que les générations plus anciennes analysent également toutes les précédentes. Pour réduire le nombre de pauses de récupération de place pour les objets à longue durée de vie, la génération la plus ancienne dispose d'
un ensemble supplémentaire de conditions .
Les seuils standard pour les générations sont définis respectivement sur
700, 10 10
, mais vous pouvez toujours les modifier Ă l'aide des fonctions
gc.get_threshold gc.set_threshold
.
Algorithme de recherche de boucle
Une description complĂšte de l'algorithme de recherche de boucle nĂ©cessitera un article sĂ©parĂ©. En bref, GC parcourt chaque objet des gĂ©nĂ©rations sĂ©lectionnĂ©es et supprime temporairement tous les liens d'un seul objet (tous les liens auxquels cet objet fait rĂ©fĂ©rence). AprĂšs un passage complet, tous les objets avec un nombre de liens infĂ©rieur Ă deux sont considĂ©rĂ©s comme inaccessibles Ă partir de python et peuvent ĂȘtre supprimĂ©s.
Pour une compréhension plus approfondie, je recommande de lire (la note du traducteur: matériel en anglais) la
description originale de l'algorithme de Neil Schemenauer et la fonction de
collect
des
sources CPython . Une description de
Quora et un
article sur le garbage collector peuvent Ă©galement ĂȘtre utiles.
Il convient de noter que le problÚme avec les destructeurs décrit dans la description originale de l'algorithme a été corrigé depuis Python 3.4 (plus de détails dans
PEP 442 ).
Conseils d'optimisation
Les boucles se produisent souvent dans des tĂąches rĂ©elles; elles peuvent ĂȘtre trouvĂ©es dans des problĂšmes avec des graphiques, des listes liĂ©es ou dans des structures de donnĂ©es oĂč vous devez garder une trace des relations entre les objets. Si votre programme a une charge Ă©levĂ©e et demande des retards, il est prĂ©fĂ©rable d'Ă©viter les boucles si possible.
Dans les endroits oĂč vous utilisez sciemment des liens circulaires, vous pouvez utiliser des liens "faibles". Les liens faibles sont implĂ©mentĂ©s dans le module
faibles rĂ©fĂ©rences et, contrairement aux liens rĂ©guliers, n'affectent en aucune façon le compteur de liens. Si l'objet avec des rĂ©fĂ©rences faibles s'avĂšre ĂȘtre supprimĂ©, alors
None
renvoyé à la place.
Dans certains cas, il est utile de désactiver la construction automatique par le module gc et de l'appeler manuellement. Pour ce faire, il suffit d'appeler
gc.disable()
puis d'appeler
gc.collect()
manuellement.
Comment rechercher et déboguer des liens circulaires
Le dĂ©bogage des boucles peut ĂȘtre pĂ©nible, surtout si votre code utilise de nombreux modules tiers.
Le module gc fournit des fonctions d'assistance qui peuvent aider au débogage. Si les paramÚtres GC sont définis sur l'indicateur
DEBUG_SAVEALL
, tous les objets inaccessibles seront ajoutés à la liste
gc.garbage
.
import gc gc.set_debug(gc.DEBUG_SAVEALL) print(gc.get_count()) lst = [] lst.append(lst) list_id = id(lst) del lst gc.collect() for item in gc.garbage: print(item) assert list_id == id(item)
Une fois que vous avez identifiĂ© le problĂšme, il peut ĂȘtre visualisĂ© en utilisant
objgraph .

Conclusion
Le processus de récupération de place principal est effectué par un algorithme de comptage de liens, qui est trÚs simple et sans paramÚtres. Un algorithme supplémentaire est utilisé uniquement pour rechercher et supprimer des objets avec des références circulaires.
Vous ne devez pas vous lancer dans une optimisation prématurée du code pour le garbage collector; en pratique, les problÚmes de garbage collection sont assez rares.
PS: je suis l'auteur de cet article, vous pouvez poser toutes vos questions.