Discussion sur Python => Que pensez-vous du gc ???

Discussion sur Python => Que pensez-vous du gc ??? - Python - Programmation

Marsh Posté le 06-10-2007 à 20:12:59    

Bonjour à tous,
 
Je me suis mis à Python depuis maintenant 2 mois et je continue à bien apprécier ce langage. Mais comme je viens du C j'avais des habitudes que j'ai du mal à perdre. Et là, je m'interroge sur le garbage collector et j'ai une question assez philosophique car je me demande si on peut s'y fier ou s'il vaut mieux garder les habitudes du C et penser à libérer soi-même les objets que l'on crée.
 
J'ai pu vérifier que généralement le garbage collector fonctionne bien. Par exemple si je mets une trace dans les constructeur et destructeur de mes objets puis que je crée une instance d'un objet que j'affecte à une variable je vois bien la création se faire. Puis si j'affecte la variable à autre chose je vois aussi la destruction se faire de façon automatique. Mais j'ai aussi réussi à le piéger en créant une classe style "élément de liste chaînée" et en faisant pointer le next de mon élément sur moi-même style

Code :
  1. class liste:
  2.    def __init__(self):
  3.        print "init"
  4.        self.next=None
  5.    def __del__(self):
  6.        print "del"


 
Test 1

Code :
  1. a=liste()     # => je vois bien apparaître le "init"
  2. a=5           # => L'instance de "liste" n'ayant plus de raison d'être, le "del" est fait par le gc et je le vois apparaître à l'écran


 
 
Test 2

Code :
  1. a=liste()    #  => je vois bien apparaître le "init"
  2. a.next=a     # Hop, piégé le gc !!!
  3. a=5          # => Ca ne change rien car il reste en mémoire un "truc" qui référence mon instance et celle-ci n'est plus libérée


 
Donc question d'éthique => vaut-il mieux laisser faire le gc (au risque de se faire avoir par un truc comme ça) ou vaut-il mieux se préoccuper soi-même de la libération de ses objets (surtout quand on crée des objets qui vont en créer d'autres en cascade) ???


Message édité par Sve@r le 06-10-2007 à 20:59:40

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

Marsh Posté le 06-10-2007 à 20:12:59   

Reply

Marsh Posté le 06-10-2007 à 23:03:39    

Ne t'inquiètes pas pour ça, le GC python est parfaitement capable de le gérer.
 
Le truc, c'est qu'il y a 2 parties dans le GC python:
 
Le Refcounting GC, qui est le GC habituel. Quand tu crées une référence sur un objet (que tu lui donnes un nom par exemple) le refcount augmente, quand cette référence est déscopée ou dégagée (en pop() and un objet d'une liste sans le récupérer) elle est décrémentée.
 
Quand une référence arrive à 0, l'objet est immédiatement détruit (ce qui se passe dans ton test 1). À noter que cette destruction immédiate est simplement un effet du refcounting GC, si la techno du GC était différente (generational GC comme en Java ou en C# par exemple) elle pourrait être effectuée beaucoup plus tard.
 
Ou même, le refcounter pourrait simplement "marquer" des objets comme à détruire et les détruire plus tard tous en même temps, ou bien en conserver certains au cas où tu réalloues le même objet par la suite (c'est d'ailleurs fait pour certaines chaînes de caractères et pas mal d'entiers)
 
Mais comme tu as pu le voir, ça ne marche pas pour les références circulaires. Pour ça, Python a un Cyclic Garbage Collector qui existe uniquement pour supprimer les cycles (des objets qui se référencent les uns les autres, mais pas accessibles depuis le programme "vivant).
 
Le truc, c'est que comme cette détection cyclique coûte cher (il faut parcourir tout l'arbre d'objet afin de vérifier lesquels sont accessibles et lesquels ne le sont pas), donc il ne tourne qu'à intervalles réguliers (et pas en permanence), et probablement à partir d'une certaine taille mémoire consommée.
 
Sur ton essai, il n'a pas le temps de se lancer (et de toute façon il a une caractéristique dont je parlerais à la fin qui fait que dans ce cas précis il ne ferait rien), mais tu peux l'activer manuellement en appellant "collect" dans le module "gc".
 
Enfin, avec ton exemple tu ne pourras pas le voir, parce que le Cyclic GC ne collecte pas les objets ayant une méthode "__del__" (__del__ peut avoir de gros effets de bord, Python ne sachant pas dans quel ordre les exécuter il refusera de détruire un cycle contenant même un seul objet avec une méthode __del__).
 
Au final:

  • Si aucun objet n'a de méthode __del__, le GC se démerdera très bien tout seul pour trouver et nettoyer les cycles
  • Si tu as des objets avec une méthode __del__ (ce qui est très rare), évite de créer des cycles les impliquant, ou tu devras les nettoyer manuellement

Message cité 1 fois
Message édité par masklinn le 06-10-2007 à 23:04:37

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

Marsh Posté le 07-10-2007 à 12:50:14    

masklinn a écrit :

Ne t'inquiètes pas pour ça, le GC python est parfaitement capable de le gérer.
 
Le truc, c'est qu'il y a 2 parties dans le GC python:
 
Le Refcounting GC, qui est le GC habituel. Quand tu crées une référence sur un objet (que tu lui donnes un nom par exemple) le refcount augmente, quand cette référence est déscopée ou dégagée (en pop() and un objet d'une liste sans le récupérer) elle est décrémentée.
 
Quand une référence arrive à 0, l'objet est immédiatement détruit (ce qui se passe dans ton test 1). À noter que cette destruction immédiate est simplement un effet du refcounting GC, si la techno du GC était différente (generational GC comme en Java ou en C# par exemple) elle pourrait être effectuée beaucoup plus tard.
 
Ou même, le refcounter pourrait simplement "marquer" des objets comme à détruire et les détruire plus tard tous en même temps, ou bien en conserver certains au cas où tu réalloues le même objet par la suite (c'est d'ailleurs fait pour certaines chaînes de caractères et pas mal d'entiers)
 
Mais comme tu as pu le voir, ça ne marche pas pour les références circulaires. Pour ça, Python a un Cyclic Garbage Collector qui existe uniquement pour supprimer les cycles (des objets qui se référencent les uns les autres, mais pas accessibles depuis le programme "vivant).
 
Le truc, c'est que comme cette détection cyclique coûte cher (il faut parcourir tout l'arbre d'objet afin de vérifier lesquels sont accessibles et lesquels ne le sont pas), donc il ne tourne qu'à intervalles réguliers (et pas en permanence), et probablement à partir d'une certaine taille mémoire consommée.
 
Sur ton essai, il n'a pas le temps de se lancer (et de toute façon il a une caractéristique dont je parlerais à la fin qui fait que dans ce cas précis il ne ferait rien), mais tu peux l'activer manuellement en appellant "collect" dans le module "gc".
 
Enfin, avec ton exemple tu ne pourras pas le voir, parce que le Cyclic GC ne collecte pas les objets ayant une méthode "__del__" (__del__ peut avoir de gros effets de bord, Python ne sachant pas dans quel ordre les exécuter il refusera de détruire un cycle contenant même un seul objet avec une méthode __del__).
 
Au final:

  • Si aucun objet n'a de méthode __del__, le GC se démerdera très bien tout seul pour trouver et nettoyer les cycles
  • Si tu as des objets avec une méthode __del__ (ce qui est très rare), évite de créer des cycles les impliquant, ou tu devras les nettoyer manuellement


Ouaouh !!! Super réponse !!!
 
C'est pas facile à se mettre dans la tête que cela marchera à condition que je ne fasse rien pour le voir marcher (un peu comme la mécanique quantique  ;) ). Mais bon, si tu dis que ça marche je te crois.
 
Merci  :jap:


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

Marsh Posté le 07-10-2007 à 14:11:24    

Sve@r a écrit :

 

Ouaouh !!! Super réponse !!!

 

C'est pas facile à se mettre dans la tête que cela marchera à condition que je ne fasse rien pour le voir marcher (un peu comme la mécanique quantique  ;) ). Mais bon, si tu dis que ça marche je te crois.


Tu peux le voir marcher en utilisant gc.collect() à la main, d'ailleurs tu peux aussi le voir ne pas marcher en utilisant gc.collect ;)

 
Code :
  1. Python 2.5 (r25:51908, Sep 19 2006, 09:52:17) [MSC v.1310 32 bit (Intel)] on win32
  2. Type "copyright", "credits" or "license()" for more information.
  3. >>> import gc
  4. >>> help(gc.collect)
  5. Help on built-in function collect in module gc:
  6.  
  7. collect(...)
  8.    collect([generation]) -> n
  9.    
  10.    With no arguments, run a full collection.  The optional argument
  11.    may be an integer specifying which generation to collect.  A ValueError
  12.    is raised if the generation number is invalid.
  13.    
  14.    The number of unreachable objects is returned.
  15.  
  16. >>> class Foo(object):
  17.     def __init__(self):
  18.         print "init"
  19.         self.next = None
  20.  
  21.         
  22. >>> class Bar(object):
  23.     def __init__(self):
  24.         print "init"
  25.         self.next = None
  26.     def __del__(self):
  27.         print "destroy"
  28.  
  29.         
  30. >>> foo = Foo()
  31. init
  32. >>> foo.next = foo
  33. >>> bar = Bar()
  34. init
  35. >>> bar.next = bar
  36. >>> foo = None
  37. >>> bar = None
  38. >>> gc.collect()
  39. 4
  40. >>> gc.garbage
  41. [<__main__.Bar object at 0x011751D0>]
  42. >>> # Ici, on voit que collect() a trouvé un total de 4 références "perdues" (la référence circulaire de foo vers foo et la référence de bar vers bar)
  43. >>> # gc.garbage par contre stocke (cf doc) des objets que collect() a trouvé "unreachable" mais n'a pas pu libérer, on voit donc que collect() a trouvé foo et bar, et a pu libérer foo mais pas bar
  44. >>> # ce qui est ce qu'on attendait
  45. >>> # On récupère notre instance de bar
  46. >>> bar = gc.garbage[0]
  47. >>> # on vide la garbage list (elle conserve une référence sur notre objet, ce qu'on ne veut pas)
  48. >>> del gc.garbage[:]
  49. >>> # on casse la référence
  50. >>> bar.next = None
  51. >>> # et on détruit notre unique référence sur bar
  52. >>> bar = None
  53. destroy
  54. >>>


gc étant, bien sûr, une simple interface vers le Cyclic GC de Python, permettant également de désactiver (gc.disable()) ou réactiver (gc.enable()) celui-ci. Rarement utile, mais peut l'être de temps en temps.

Message cité 1 fois
Message édité par masklinn le 07-10-2007 à 14:12:38

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

Marsh Posté le 08-10-2007 à 05:05:01    

masklinn a écrit :


gc étant, bien sûr, une simple interface vers le Cyclic GC de Python, permettant également de désactiver (gc.disable()) ou réactiver (gc.enable()) celui-ci. Rarement utile, mais peut l'être de temps en temps.

 

C'est très utile si tu veut faire un éditeur texte, tu le désactive et tu met un bouton en bas de ton interface pour le lancer manuellement à la place [:bien]

Message cité 1 fois
Message édité par 0x90 le 08-10-2007 à 05:05:24

---------------
Me: Django Localization, Yogo Puzzle, Chrome Grapher, C++ Signals, Brainf*ck.
Reply

Marsh Posté le 08-10-2007 à 08:15:56    

0x90 a écrit :


 
C'est très utile si tu veut faire un éditeur texte, tu le désactive et tu met un bouton en bas de ton interface pour le lancer manuellement à la place [:bien]


Stop that, it's very silly :o
 
(en réalité, c'est parfois utilisé quand on sait qu'on a pas de références circulaires, qu'on a beaucoup d'objets vivant en même temps, dans le système, et qu'on veut éviter d'avoir le GC qui tourne trop souvent)


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

Sujets relatifs:

Leave a Replay

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