70 votes

Décharger un module en Python

TL/DR :

import gc, sys

print len(gc.get_objects()) # 4073 objects in memory

# Attempt to unload the module

import httplib
del sys.modules["httplib"]
httplib = None

gc.collect()
print len(gc.get_objects()) # 6745 objects in memory

UPDATE J'ai contacté les développeurs de Python à propos de ce problème et, en effet, c'est il ne sera pas possible de décharger un module complètement "dans les cinq prochaines années". (voir le lien)

Veuillez accepter que Python ne supporte effectivement pas le déchargement des modules pour des problèmes techniques graves, fondamentaux, insurmontables, en 2.x.


Au cours de ma récente recherche d'une fuite de mémoire dans mon application, j'ai réduit le problème à des modules, à savoir mon incapacité à collecte des déchets un module non chargé. Utilisation de tout La méthode indiquée ci-dessous pour décharger un module laisse des milliers d'objets en mémoire. En d'autres termes, je ne peux pas décharger un module en Python...

Le reste de la question consiste à essayer de récupérer un module d'une manière ou d'une autre.

Essayons :

import gc
import sys

sm = sys.modules.copy()  # httplib, which we'll try to unload isn't yet 
                         # in sys.modules, so, this isn't the source of problem

print len(gc.get_objects()) # 4074 objects in memory

Enregistrons une copie de sys.modules pour tenter de le restaurer plus tard. Il s'agit donc d'une base de référence de 4074 objets. Nous devrions idéalement y revenir d'une manière ou d'une autre.

Importons un module :

import httplib
print len(gc.get_objects()) # 7063 objects in memory

Nous en sommes à 7K objets non-déchets. Essayons de supprimer httplib de sys.modules .

sys.modules.pop('httplib')
gc.collect()
print len(gc.get_objects()) # 7063 objects in memory

Eh bien, ça n'a pas marché. Hmm, mais n'y a t-il pas une référence dans __main__ ? Oh, oui :

del httplib
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

Hourra, moins 300 objets. Mais pas de cigare, ça fait bien plus que 4000 objets originaux. Essayons de restaurer sys.modules de la copie.

sys.modules = sm
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

Hmmm, eh bien, c'était inutile, pas de changement... Peut-être que si on efface les globales...

globals().clear()
import gc # we need this since gc was in globals() too
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

des locaux ?

locals().clear()
import gc # we need this since gc was in globals() too
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

Et si et si on imported un module à l'intérieur de exec ?

local_dict = {}
exec 'import httplib' in local_dict
del local_dict
gc.collect()
print len(gc.get_objects())  # back to 7063 objects in memory

Maintenant, ce n'est pas juste, il l'a importé en __main__ Pourquoi ? Il n'aurait jamais dû quitter la local_dict ... Argh ! Nous sommes de retour à l'importation complète httplib . Peut-être que si on le remplaçait par un objet factice ?

from types import ModuleType
import sys
print len(gc.get_objects())  # 7064 objects in memory

Bloody..... !!

sys.modules['httplib'] = ModuleType('httplib')
print len(gc.get_objects())  # 7066 objects in memory

Modules morts, morts !

import httplib
for attr in dir(httplib):
    setattr(httplib, attr, None)
gc.collect()
print len(gc.get_objects())  # 6749 objects in memory

Ok, après toutes les tentatives, le meilleur résultat est +2675 (presque +50%) par rapport au point de départ... Et ça, c'est juste pour un module... Qui n'a même pas quelque chose de gros à l'intérieur...

Ok, maintenant sérieusement, où est mon erreur ? Comment puis-je décharger un module et effacer tout son contenu ? Ou est-ce que les modules de Python sont une fuite de mémoire géante ?

Source complète dans un format plus simple à copier : http://gist.github.com/450606

0 votes

1. Avez-vous déjà examiné, un par un, quels sont exactement les objets restants ? 2. vous essayez de tester en utilisant les modules de "python". Avez-vous essayé la même approche avec un gros module naïf créé par vous ? un module qui ne fait référence à rien d'autre que lui-même ?

26voto

Daniel Stutzbach Points 20026

Python ne supporte pas le déchargement des modules.

Cependant, à moins que votre programme ne charge un nombre illimité de modules au fil du temps, ce n'est pas la source de votre fuite de mémoire. Les modules sont normalement chargés une fois au démarrage et c'est tout. Votre fuite de mémoire se situe très probablement ailleurs.

Dans le cas peu probable où votre programme chargerait réellement un nombre illimité de modules au fil du temps, vous devriez probablement revoir la conception de votre programme ;-)

2 votes

Oui, il charge un nombre raisonnablement illimité de modules - c'est un serveur d'applications web qui accepte les nouvelles révisions de son propre code source et le recharge (c'est une tâche web assez standard). La fuite EST due au fait que l'ancien code est toujours présent en mémoire, même s'il est remplacé, même s'il est inaccessible...

0 votes

Python supporte le déchargement des modules. Ils sont ramassés, comme tous les autres objets de Python.

2 votes

@Slava : Tu pourrais vouloir jeter un coup d'oeil au code source pour mod_python Ce dernier possède son propre importateur, qui est conçu pour gérer le rechargement des modules sans produire de fuites de mémoire. Il y a peut-être du code là-dedans que vous pourriez utiliser.

6voto

Je ne suis pas sûr pour Python, mais dans d'autres langages, appeler l'équivalent de gc.collect() fait no libérer la mémoire inutilisée - il ne libérera cette mémoire que si/quand la mémoire est réellement nécessaire.

Sinon, il est logique que Python garde les modules en mémoire pour le moment, au cas où ils devraient être chargés à nouveau.

0 votes

Le problème est que je dois les remplacer par de nouvelles versions. Et même lorsque je les remplace 1 à 1 par des modules de même taille, l'utilisation de la mémoire augmente (fuites)... Merci pour la suggestion, cependant.

5voto

ilias iliadis Points 131

Python's small object manager rarely returns memory back to the Operating System. De aquí y aquí . Donc, à proprement parler, python a (par conception) une sorte de fuite de mémoire, même lorsque les objets sont "collectés par gc".

5voto

modesitt Points 2272

Je ne trouve pas de point de vue faisant autorité à ce sujet dans python3 (10 ans plus tard) (maintenant python3.8 ). Cependant, nous pouvons faire mieux en termes de pourcentage maintenant.

import gc
import sys

the_objs = gc.get_objects()
print(len(gc.get_objects())) # 5754 objects in memory
origin_modules = set(sys.modules.keys())
import http.client # it was renamed ;)

print(len(gc.get_objects())) # 9564 objects in memory
for new_mod in set(sys.modules.keys()) - origin_modules:
    del sys.modules[new_mod]
    try:
        del globals()[new_mod]
    except KeyError:
        pass
    try:
        del locals()[new_mod]
    except KeyError:
        pass
del origin_modules
# importlib.invalidate_caches()  happens to not do anything
gc.collect()
print(len(gc.get_objects())) # 6528 objects in memory 

n'a augmenté que de 13 %. Si vous regardez le type d'objets qui sont chargés dans le nouveau gc.get_objects certains d'entre eux sont des builtins, des codes sources, random.* des services publics, datetime les services publics, etc. Je laisse surtout ceci ici comme une mise à jour pour commencer la conversation pour @shuttle et supprimera si nous pouvons faire plus de progrès.

1 votes

J'apprécie l'effort fourni ! Il est très intéressant de voir l'évolution en pourcentage

0voto

Glenn Maynard Points 24451

(Vous devriez essayer d'écrire des questions plus concises ; je n'ai lu que le début et survolé le reste). Je vois un problème simple au début :

sm = sys.modules.copy()

Vous avez fait une copie de sys.modules, donc maintenant votre copie a une référence au module - donc bien sûr il ne sera pas collecté. Vous pouvez voir ce qui s'y réfère avec gc.get_referrers.

Cela fonctionne bien :

# module1.py
class test(object):
    def __del__(self):
        print "unloaded module1"
a = test()

print "loaded module1"

.

# testing.py
def run():
    print "importing module1"
    import module1
    print "finished importing module1"

def main():
    run()
    import sys
    del sys.modules["module1"]
    print "finished"

if __name__ == '__main__':
    main()

module1 est déchargé dès que nous le retirons de sys.modules, car il n'y a plus de références au module. (En faisant module1 = None après l'importation fonctionnerait également - j'ai simplement placé l'importation dans une autre fonction pour plus de clarté. Tout ce que vous avez à faire est de supprimer vos références à celle-ci).

Dans la pratique, il est un peu difficile de faire cela, pour deux raisons :

  • Pour pouvoir collecter un module, toutes les références au module doivent être inaccessibles (comme pour la collecte de tout objet). Cela signifie que tous les autres modules qui l'ont importé doivent également être déréférencés et rechargés.
  • Si vous supprimez un module de sys.modules alors qu'il est toujours référencé ailleurs, vous avez créé une situation inhabituelle : le module est toujours chargé et utilisé par le code, mais le chargeur de module n'en a plus connaissance. La prochaine fois que vous importerez le module, vous n'obtiendrez pas de référence à l'existant (puisque vous en avez supprimé l'enregistrement), et il chargera donc une deuxième copie coexistante du module. Cela peut causer de sérieux problèmes de cohérence. Assurez-vous donc qu'il n'y a plus de références au module avant de le supprimer définitivement de sys.modules.

Il y a quelques problèmes délicats à utiliser ceci en général : détecter quels modules dépendent du module que vous déchargez ; savoir s'il est correct de les décharger aussi (cela dépend fortement de votre cas d'utilisation) ; gérer le threading tout en examinant tout ceci (jetez un coup d'oeil à imp.acquire_lock), et ainsi de suite.

Je pourrais imaginer un cas où cela pourrait être utile, mais la plupart du temps, je recommanderais simplement de redémarrer l'application si son code change. Vous ne feriez probablement que vous donner des maux de tête.

11 votes

Bon, je ne veux pas être snyde, mais vous auriez dû lire la question, ou au moins le mot "complètement" dans le titre (ou au moins les balises). Le problème n'est pas que je ne veux pas recharger, le problème est le suivant fuite de mémoire associés à tout type (répertorié) de suppression de module (y compris ceux que vous avez proposés, qui sont énumérés dans ma question, ainsi que des dizaines d'autres). En fait, j'ai ajouté sys.modules.copy() à un stade très tardif, la suppression ne change rien (essayez vous-même).

1 votes

La source, pour essayer : gist.github.com/450606 . Essayez de supprimer le module sys.modules.copy et vous verrez qu'il y a toujours une augmentation de plus de 50% des objets même si toutes les références au module ont été supprimées.

0 votes

Voir ici pour un résumé de ce qui ne va pas (en utilisant votre code) : gist.github.com/450726 . Je n'essaye pas de charger/décharger sys puisque nous travaillons sur sys.modules donc j'utilise httplib - vous pouvez en essayer d'autres.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X