[Resolu] Gestion de la mémoire

Gestion de la mémoire [Resolu] - Python - Programmation

Marsh Posté le 19-03-2009 à 11:59:29    

Bonjour à tous - Je viens ici demander une aide suite à un problème qui me turlupine. A force de chercher, j'ai réussi à ramener le problème à un exemple tout simple qui servira de démo facilement compréhensible
 
Je dois gérer un objet texte. A cet objet y vont s'adjoindre diverses méthodes me permettant de récupérer des infos sur ce texte comme nb lignes, de mots, etc.
A un moment donné, je dois renvoyer un paragraphe du texte. Je me dis en moi-même "un paragraphe étant un mini-texte, si je renvoie une nouvelle instance de mon objet ne contenant que ledit paragraphe, cette instance bénéficiera de toutes les méthodes utiles (nb de lignes, de mots, etc).
 
Mais c'est là que ça se corse. Voici un petit exemple tout simple montrant le problème. Pour simplifier, j'ai juste créé un objet qui stocke un tableau de nombres et créé une méthode "sort" qui renvoie un nouvel objet contenant le tableau trié

Code :
  1. #!/usr/bin/env python
  2. # coding: Latin-1 -*-
  3. class cGestion:
  4.     def __init__(self):
  5.         print "init: %s" % repr(self)
  6.         self.tab=[]
  7.     def __del__(self):
  8.         print "del: %s" % repr(self)
  9.         del self.tab[:]
  10.         del self.tab
  11.     def __str__(self):
  12.         return "[%s]: %s" % (repr(self), self.tab)
  13.     def append(self, k):
  14.         self.tab.append(k)
  15.     def sort(self):
  16.         n=cGestion()
  17.         for k in self.tab:
  18.             n.append(k)
  19.         n.tab.sort()
  20.         return n
  21. # Création élément de base
  22. base=cGestion()
  23. base.append(2)
  24. base.append(1)
  25. base.append(0)
  26. print "\nbase: ", base
  27. # Test 1: copie triée de l'objet de base - la copie est mémorisée
  28. cp=base.sort()
  29. print "\ntest 1:" , cp, cp.tab
  30. del cp
  31. # Test 2: affichage juste d'une instance triée
  32. print "\ntest 2:", base.sort()
  33. # Test 3: affichage d'un élément de l'instance triée
  34. print "\ntest 3:", base.sort().tab


 
A la base, je crée mon objet. Les affichages sont ok
Au test 1, je récupère une copie triée de mon objet. Aucun souci
Au test 2, j'affiche juste la copie triée de mon objet mais ne l'ai pas sauvegardée. Cependant aucun souci
Au test 3, là j'affiche le tableau stocké dans la copie au moment de la copie et là, je me rends compte que le destructeur a déjà fait son office et le tableau est vide.
 
Ce qui est intéressant, c'est que si dans le destructeur on supprime le nettoyage du tableau (del self.tab[:]), là tout marche bien. Je sais que Python possède une gestion de la mémoire et que je ne devrais pas me préoccuper de libération de mon tableau interne mais je suis issu du C et c'est plus fort que moi. Si mon constructeur crée un élément, mon destructeur supprime l'élément.
 
Quelqu'un aurait un conseil à me donner ?
 
Merci


Message édité par Sve@r le 21-03-2009 à 11:35:23

---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 19-03-2009 à 11:59:29   

Reply

Marsh Posté le 19-03-2009 à 13:41:25    

Le comportement est tout à fait normal : dans le test 3, tu n'as aucune référence mémorisée vers l'objet rejeté par base.sort(). Quand tu affiches base.sort().tab, Python appelle automatiquement le destructeur vers l'objet rejeté par base.sort() (puisqu'aucune référence n'est faite vers cet objet), et comme ton destructeur supprime l'élément tab de cet objet, par extension, il supprime tout avant que tu l'affiches.

Reply

Marsh Posté le 19-03-2009 à 14:39:36    

guybrush02 a écrit :

Le comportement est tout à fait normal : dans le test 3, tu n'as aucune référence mémorisée vers l'objet rejeté par base.sort(). Quand tu affiches base.sort().tab, Python appelle automatiquement le destructeur vers l'objet rejeté par base.sort() (puisqu'aucune référence n'est faite vers cet objet), et comme ton destructeur supprime l'élément tab de cet objet, par extension, il supprime tout avant que tu l'affiches.


 
Yo merci d'avoir répondu si vite. Tu décris effectivement ce dont je me doutais (surtout que je vois le "del" apparaître avant l'affichage du "test3" ). Mais comment faire alors pour
1) rester propre niveau gestion de la mémoire
2) pouvoir récupérer quand-même mon "base.sort().tab"
???


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 19-03-2009 à 18:01:29    

1) Ne pas gérer la mémoire toi-même quand ce n'est pas nécessaire.  
2) Respecter le point 1 ^^
 
 
Définir un del sur ton objet n'ajoute rien pour le garbage collector, vu que tu ne fais (à peu de choses près) que redéfinir un comportement par défaut (la variation faisant que ça ne marche pas ici). Si tu souhaites libérer de la mémoire, une simple suppression de la variable référençant cet objet est suffisante pour avoir un nettoyage "en cascade" si nécessaire, traité par le garbage collector automatiquement au moment le plus opportun.

Message cité 2 fois
Message édité par guybrush02 le 19-03-2009 à 18:03:14
Reply

Marsh Posté le 19-03-2009 à 18:20:07    

guybrush02 a écrit :

Définir un del sur ton objet n'ajoute rien pour le garbage collector


Ca retire des choses même. Ca empêche le détecteur de références circulaires de bosser correctement par exemple, un cycle contenant un objet (ou plus) ayant une méthode __del__ définie ne sera pas détecté, donc pas supprimé, et devra être intégralement nettoyé manuellement.

 

Il ne faut jamais définir de __del__ quand on est pas certain que c'est exactement ce dont on a besoin (et globalement c'est uniquement le cas pour des ressources rares: handlers de fichiers ou de sockets, connections, ...).

Message cité 1 fois
Message édité par masklinn le 19-03-2009 à 18:20:31

---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 20-03-2009 à 15:12:31    

guybrush02 a écrit :

1) Ne pas gérer la mémoire toi-même quand ce n'est pas nécessaire.  
2) Respecter le point 1 ^^
 
Définir un del sur ton objet n'ajoute rien pour le garbage collector, vu que tu ne fais (à peu de choses près) que redéfinir un comportement par défaut (la variation faisant que ça ne marche pas ici). Si tu souhaites libérer de la mémoire, une simple suppression de la variable référençant cet objet est suffisante pour avoir un nettoyage "en cascade" si nécessaire, traité par le garbage collector automatiquement au moment le plus opportun.


 
 

masklinn a écrit :


Ca retire des choses même. Ca empêche le détecteur de références circulaires de bosser correctement par exemple, un cycle contenant un objet (ou plus) ayant une méthode __del__ définie ne sera pas détecté, donc pas supprimé, et devra être intégralement nettoyé manuellement.
 
Il ne faut jamais définir de __del__ quand on est pas certain que c'est exactement ce dont on a besoin (et globalement c'est uniquement le cas pour des ressources rares: handlers de fichiers ou de sockets, connections, ...).


 
Et ce garbage est efficace ? Imaginons que je fasse une boucle style

Code :
  1. for i in range(1000):
  2.    a=cGestion()
  3.    ...


 
Sera-ce plus efficace que

Code :
  1. for i in range(1000):
  2.    a=cGestion()
  3.    ...
  4.    del a


???


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 20-03-2009 à 15:24:19    

Sve@r a écrit :

Et ce garbage est efficace ? Imaginons que je fasse une boucle style


Le GC tourne dans les deux cas, il n'y a aucune raison pour qu'il y ait une différence significative, et la 2e version a une instruction (del) de plus que l'autre [:spamafote]

Code :
  1. >>> class A(object):
  2.     def __init__(self):
  3.         self.tab = []
  4.  
  5.         
  6. >>> def testref():
  7.     for i in range(1000):
  8.         a = A()
  9.  
  10.         
  11. >>> def testdel():
  12.     for i in range(1000):
  13.         a = A()
  14.         del a
  15.  
  16.         
  17. >>> from timeit import Timer
  18. >>> t1 = Timer('testref()', 'from __main__ import testref')
  19. >>> t2 = Timer('testdel()', 'from __main__ import testdel')
  20. >>> t1.repeat(number=10000)
  21. [11.701371822396425, 11.629359038648772, 11.613105855632483]
  22. >>> t2.repeat(number=10000)
  23. [11.716563138611193, 11.681633000842297, 11.773158599766163]
  24. >>>


[:spamafote]
 
Faut arrêter les tentatives de microoptimisation à la con dans ce genre, surtout quand tu ne connais pas le runtime et que tu n'as aucune évidence d'un problème de perf (sans même parler de ne pas savoir où est le problème s'il y en a un)


---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 20-03-2009 à 15:29:04    

Et accessoirement, le __del__ qui ne sert à rien a clairement un impact fort sur ce microbench à la con (dans une vraie application il serait probablement invisible) dans la mesure où le runtime doit appeler une fonction Python (alors que la suppression d'origine se fait intégralement en C) et exécuter un slice et deux del à l'intérieur:

Code :
  1. >>> class B(object):
  2.     def __init__(self):
  3.         self.tab = []
  4.     def __del__(self):
  5.         del self.tab[:]
  6.         del self.tab
  7.  
  8.         
  9. >>> def testrefB():
  10.     for i in range(1000):
  11.         b = B()
  12.  
  13.         
  14. >>> def testdelB():
  15.     for i in range(1000):
  16.         b = B()
  17.         del b
  18.  
  19.         
  20. >>> t3 = Timer('testrefB()', 'from __main__ import testrefB')
  21. >>> t4 = Timer('testdelB()', 'from __main__ import testdelB')
  22. >>> t3.repeat(number=10000)
  23. [19.552072400263171, 19.476673152593378, 19.429103419568719]
  24. >>> t4.repeat(number=10000)
  25. [20.073180809292751, 20.089330068486333, 20.225080003184758]
  26. >>>


(c'est exactement le même code qu'au dessus, j'ai juste rajouté ton __del__ dans la classe)


Message édité par masklinn le 20-03-2009 à 15:29:21

---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 20-03-2009 à 22:23:05    

masklinn a écrit :


Faut arrêter les tentatives de microoptimisation à la con dans ce genre


Non tu ne comprends pas. Je ne cherche pas à optimiser, je cherche juste à être certain que tout ce que j'alloue en mémoire est bien libéré une fois que ce n'est plus utile. Je t'ai dit que j'étais issu du C (et aussi du C++). Si je crée une méthode "__del__", je peux contrôler (print) qu'elle est bien appelée lorsque j'appelle mon del <objet>. Si je ne la crée pas, je ne vois rien et ça, ça me gène.

Message cité 1 fois
Message édité par Sve@r le 20-03-2009 à 22:23:50

---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 20-03-2009 à 22:31:55    

Sve@r a écrit :

Non tu ne comprends pas. Je ne cherche pas à optimiser, je cherche juste à être certain que tout ce que j'alloue en mémoire est bien libéré une fois que ce n'est plus utile. Je t'ai dit que j'étais issu du C (et aussi du C++). Si je crée une méthode "__del__", je peux contrôler (print) qu'elle est bien appelée lorsque j'appelle mon del <objet>. Si je ne la crée pas, je ne vois rien et ça, ça me gène.


Python a un GC, le GC se démerde tout seul, tant que tu n'as pas de problème de mémoire ne te préoccupes pas de ça [:spamafote]

 

Et accessoirement, del ne supprime rien, il décrémente le refcount de l'objet de 1, donc si tu pars avec l'idée que c'est équivalent à free/delete t'as déjà tout faux [:spamafote]

 

Idem pour définir __del__, ça se fait pas dans un langage à GC, sauf si tu as une ressource rare qui doit impérativement être relachée (fichier, connection db) et encore (en 2.5+ on passera par un context manager pour ce genre de trucs)

Message cité 1 fois
Message édité par masklinn le 20-03-2009 à 22:35:56

---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 20-03-2009 à 22:31:55   

Reply

Marsh Posté le 21-03-2009 à 11:34:49    

masklinn a écrit :


Python a un GC, le GC se démerde tout seul, tant que tu n'as pas de problème de mémoire ne te préoccupes pas de ça [:spamafote]
 
Et accessoirement, del ne supprime rien, il décrémente le refcount de l'objet de 1, donc si tu pars avec l'idée que c'est équivalent à free/delete t'as déjà tout faux [:spamafote]


Arf ok
 

masklinn a écrit :

Idem pour définir __del__, ça se fait pas dans un langage à GC, sauf si tu as une ressource rare qui doit impérativement être relachée (fichier, connection db) et encore (en 2.5+ on passera par un context manager pour ce genre de trucs)


Merci pour tout.  :hello:  


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed