Chaque instance d'une classe dans CPython créée à l'aide de la syntaxe de classe est impliquée dans un mécanisme de récupération de place circulaire . Cela augmente l'empreinte mémoire de chaque instance et peut entraîner des problèmes de mémoire dans les systèmes fortement chargés.
Si nécessaire, est-il possible de se passer d'un mécanisme de comptage de liens de base?
Examinons une approche qui aidera à créer des classes dont les instances ne seront supprimées qu'à l'aide du mécanisme de comptage de liens .
Un peu sur la récupération de place dans CPython
Le principal mécanisme de récupération de place en Python est le mécanisme de comptage de liens. Chaque objet contient un champ qui contient la valeur actuelle du nombre de liens vers celui-ci. Un objet est détruit dès que la valeur du compteur de référence devient nulle. Cependant, il ne permet pas de supprimer les objets contenant des références circulaires. Par exemple
lst = [] lst.append(lst) del lst
Dans de tels cas, après avoir supprimé l'objet, le compteur de références à celui-ci reste supérieur à zéro. Pour résoudre ce problème, Python dispose d'un mécanisme supplémentaire qui suit les objets et rompt les boucles dans le graphe de liens entre les objets. Il y a un bon article sur le fonctionnement du mécanisme de récupération de place dans CPython3.
Frais généraux associés au mécanisme de collecte des ordures
En règle générale, le mécanisme de récupération de place ne cause pas de problèmes. Mais certains frais généraux y sont associés:
Lorsque chaque classe de mémoire est allouée, l'en - tête PyGC_Head est ajouté : (au moins 24 octets en Python <= 3,7 et au moins 16 octets en 3,8 sur une plate-forme 64 bits.
Cela peut créer un problème de manque de mémoire si vous exécutez de nombreuses instances du même processus, dans lesquelles vous devez avoir un très grand nombre d'objets avec un nombre relativement petit d'attributs et la taille de la mémoire est limitée.
Est-il parfois possible de se limiter au mécanisme de base du comptage de liens?
Le mécanisme de récupération de place circulaire peut être redondant lorsque la classe représente un type de données non récursif. Par exemple, des enregistrements contenant des valeurs d'un type simple (nombres, chaînes, date / heure). Pour illustrer, considérons une classe simple:
class Point: x: int y: int
S'il est utilisé correctement, les cycles de liaison ne sont pas possibles. Bien qu'en Python, rien n'empêche de "se botter le pied":
p = Point(0, 0) px = p
Néanmoins, pour la classe Point
, on pourrait se limiter à un mécanisme de comptage de liens. Mais il n'y a pas encore de mécanisme standard pour rejeter le ramasse-miettes cyclique pour une seule classe.
CPython moderne est conçu de telle sorte que lors de la définition de classes personnalisées dans la structure responsable du type qui définit la classe personnalisée, l'indicateur Py_TPFLAGS_HAVE_GC est toujours défini. Il détermine que les instances de classe seront incluses dans le mécanisme de récupération de place. Pour tous ces objets, une fois créés, l'en - tête PyGC_Head est ajouté et ils sont inclus dans la liste des objets surveillés. Si l'indicateur Py_TPFLAGS_HAVE_GC
pas défini, seul le mécanisme de comptage de liens de base fonctionne. Cependant, une seule réinitialisation de Py_TPFLAGS_HAVE_GC
ne fonctionnera pas. Vous devrez apporter des modifications au noyau CPython responsable de la création et de la destruction des instances. Et cela reste problématique.
À propos d'une mise en œuvre
Comme exemple d'implémentation de l'idée, considérons l' dataobject
de dataobject
de classe de base du projet recordclass . En l'utilisant, vous pouvez créer des classes dont les instances ne participent pas au mécanisme de récupération de place circulaire ( Py_TPFLAGS_HAVE_GC
pas installé et, par conséquent, il n'y a pas d'en-tête PyGC_Head
supplémentaire). Ils ont exactement la même structure en mémoire que les instances de classe avec __slots__ , mais sans PyGC_Head
:
from recordclass import dataobject class Point(dataobject): x:int y:int >>> p = Point(1,2) >>> print(p.__sizeof__(), sys.getsizeof(p)) 32 32
À titre de comparaison, nous donnons une classe similaire avec __slots__
:
class Point: __slots__ = 'x', 'y' x:int y:int >>> p = Point(1,2) >>> print(p.__sizeof__(), sys.getsizeof(p)) 32 64
La différence de taille est exactement la taille de l'en PyGC_Head
tête PyGC_Head
. Pour les instances avec plusieurs attributs, une telle augmentation de la taille de sa trace dans la RAM peut être significative. Pour les instances de la classe Point
, l'ajout de PyGC_Head
augmentera sa taille de 2 fois.
Pour obtenir cet effet, un datatype
métaclasse spécial datatype
, qui fournit le réglage des sous-classes de dataobject
. En raison de la configuration, l'indicateur Py_TPFLAGS_HAVE_GC
est Py_TPFLAGS_HAVE_GC
, la taille de base de l'instance tp_basicsize augmente de la quantité nécessaire pour stocker des emplacements de champ supplémentaires. Les noms de champs correspondants sont répertoriés lorsque la classe est déclarée (la classe Point
a deux: x
et y
). Le type de datatype
métatlasse permet également de définir les valeurs des emplacements tp_alloc , tp_new , tp_dealloc , tp_free , qui implémentent les algorithmes appropriés pour créer et détruire des instances en mémoire. Par défaut, les instances manquent __weakref__ et __dict__ (comme les instances de classe avec __slots__
).
Conclusion
Comme on peut le voir, dans CPython, si nécessaire, il est possible de désactiver le mécanisme de récupération de place circulaire pour une classe particulière, lorsqu'il est certain que ses instances ne formeront pas de liens circulaires. Cela réduira leur trace en mémoire de la taille de l'en PyGC_Head
tête PyGC_Head
.