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
METTRE À JOUR J'ai contacté Python développeurs à 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 prend en effet pas en charge le déchargement des modules pour les problèmes techniques graves, fondamentaux, insurmontables, dans 2.x.
Au cours de ma récente recherche d'un memleak dans mon application, je l'ai réduit aux modules, à savoir mon incapacité à garbage collection un module déchargé. L'utilisation de n'importe quelle méthode répertorié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 est de tenter de ramasser 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
Sauvegardons une copie de sys.modules
pour tenter de le restaurer ultérieurement. Donc, ceci est une base 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 sommes jusqu'à 7K objets non-garbage. Essayons de supprimer httplib
de sys.modules
.
sys.modules.pop('httplib')
gc.collect()
print len(gc.get_objects()) # 7063 objects in memory
Eh bien, cela n'a pas fonctionné. Hmm, mais n'y a-t-il pas de référence dans __main__
? Oh oui:
del httplib
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory
Hourra, en baisse de 300 objets. Pourtant, pas de cigare, c'est bien plus de 4000 objets originaux. Essayons de restaurer sys.modules
à partir 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 nous éliminons les globaux ...
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
Qu'est-ce que .. et si nous 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é dans __main__
, Pourquoi? Il n'aurait jamais dû quitter le local_dict
... Argh! Nous revenons à httplib
entièrement importés. Peut-être que si nous le remplaçions par un objet factice?
from types import ModuleType
import sys
print len(gc.get_objects()) # 7064 objects in memory
Sanglant.....!!
sys.modules['httplib'] = ModuleType('httplib')
print len(gc.get_objects()) # 7066 objects in memory
Meurs modules, meurs !!
import httplib
for attr in dir(httplib):
setattr(httplib, attr, None)
gc.collect()
print len(gc.get_objects()) # 6749 objects in memory
D'accord, après toutes les tentatives, le meilleur est +2675 (presque + 50%) du point de départ ... C'est juste à partir d'un module ... Cela n'a même rien de gros à l'intérieur ...
Ok, maintenant sérieusement, où est mon erreur? Comment décharger un module et effacer tout son contenu? Ou les modules de Python sont-ils une fuite de mémoire géante?
Source complète sous une forme plus simple à copier: http://Gist.github.com/450606
Python ne prend pas en charge 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 probablement ailleurs.
Dans le cas peu probable où votre programme charge réellement un nombre illimité de modules au fil du temps, vous devriez probablement reconcevoir votre programme. ;-)
Je ne suis pas sûr de Python, mais dans d'autres langages, appeler l'équivalent de gc.collect()
pas libère la mémoire inutilisée - cela 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 auraient besoin d'être à nouveau chargés.
(Vous devriez essayer d'écrire des questions plus concises; je n'ai lu que le début et j'ai parcouru 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, elle ne sera pas collectée. Vous pouvez voir ce qui y fait référence 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 __== '__main__':
main()
module1 est déchargé dès que nous le supprimons de sys.modules, car il n'y a plus de références au module. (Faire module1 = None
après l'importation fonctionnerait aussi - je viens de mettre l'importation dans une autre fonction pour plus de clarté. Tout ce que vous avez à faire est de laisser tomber vos références.)
Maintenant, c'est un peu difficile de faire cela en pratique, à cause de deux problèmes:
Il y a quelques problèmes délicats à utiliser en général: détecter quels modules dépendent du module que vous déchargez; savoir s'il est acceptable de les décharger aussi (dépend fortement de votre cas d'utilisation); gérer les threads tout en examinant tout cela (jetez un œil à imp.acquire_lock), et ainsi de suite.
Je pourrais trouver 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 vous donnerez probablement juste des maux de tête.