2010-10-12 7 views
15

J'ai un problème avec mon application python, et je pense que c'est lié à la collection garbage python, même si je ne suis pas sûr ...La collecte des ordures Python peut-elle être aussi lente?

Le problème est que mon application prend beaucoup de temps pour sortir et pour passer à une fonction à la suivante.

Dans mon application, je gère des dictionnaires très volumineux, contenant des milliers de grands objets qui sont instanciés à partir de classes C++ encapsulées. J'ai mis quelques sorties d'horodatage dans mon programme, et j'ai vu qu'à la fin de chaque fonction, quand les objets créés à l'intérieur de la fonction devaient sortir du cadre, l'interprète passe beaucoup de temps avant d'appeler la fonction suivante . Et j'observe le même problème à la fin de l'application, quand le programme devrait sortir: beaucoup de temps (~ heures!) Est passé entre le dernier horodatage à l'écran et l'apparition de l'invite fraîche.

L'utilisation de la mémoire est stable, donc je n'ai pas vraiment de fuites de mémoire.

Des suggestions?

Peut-être la garbage collection de milliers d'objets C++ volumineux qui ralentissent?

Existe-t-il une méthode pour accélérer cela?

MISE À JOUR:

Merci beaucoup pour toutes vos réponses, vous m'a donné beaucoup de conseils pour déboguer mon code :-)

J'utilise Python 2.6.5 scientifiques Linux 5, un Une distribution personnalisée basée sur Red Hat Enterprise 5. En fait, je n'utilise pas SWIG pour obtenir des liaisons Python pour notre code C++, mais le framework Reflex/PyROOT. Je sais, ce n'est pas très connu en dehors de la physique des particules (mais toujours open source et librement disponible) et je dois l'utiliser parce que c'est la valeur par défaut pour notre framework principal.

Et dans ce contexte, la commande DEL du côté Python ne fonctionne pas, je l'avais déjà essayé. DEL supprime uniquement la variable python liée à l'objet C++, pas l'objet lui-même en mémoire, qui appartient toujours au côté C++ ...

... Je sais, ce n'est pas standard, je suppose, et un peu compliqué, désolé :-P

Mais suivant vos conseils, je vais profiler mon code et je reviendrai à vous avec plus de détails, comme vous l'avez suggéré.

Mise à jour supplémentaire:

Ok, suivant vos suggestions, j'instrumenté mon code avec cProfile, et j'ai découvert que fait la fonction gc.collect() est la fonction qui prend la plupart du temps en cours d'exécution !!

ici la sortie de cProfile + pstats print_stats():

 

    >>> p.sort_stats("time").print_stats(20) 
Wed Oct 20 17:46:02 2010 mainProgram.profile 

     547303 function calls (542629 primitive calls) in 548.060 CPU seconds 

    Ordered by: internal time 
    List reduced from 727 to 20 due to restriction 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     4 345.701 86.425 345.704 86.426 {gc.collect} 
     1 167.115 167.115 200.946 200.946 PlotD3PD_v3.2.py:2041(PlotSamplesBranches) 
     28 12.817 0.458 13.345 0.477 PlotROOTUtils.py:205(SaveItems) 
    9900 10.425 0.001 10.426 0.001 PlotD3PD_v3.2.py:1973(HistoStyle) 
    6622 5.188 0.001 5.278 0.001 PlotROOTUtils.py:403(__init__) 
     57 0.625 0.011 0.625 0.011 {built-in method load} 
     103 0.625 0.006 0.792 0.008 dbutils.py:41(DeadlockWrap) 
     14 0.475 0.034 0.475 0.034 {method 'dump' of 'cPickle.Pickler' objects} 
    6622 0.453 0.000 5.908 0.001 PlotROOTUtils.py:421(CreateCanvas) 
    26455 0.434 0.000 0.508 0.000 /opt/root/lib/ROOT.py:215(__getattr__) 
[...] 

>>> p.sort_stats("cumulative").print_stats(20) 
Wed Oct 20 17:46:02 2010 mainProgram.profile 

     547303 function calls (542629 primitive calls) in 548.060 CPU seconds 

    Ordered by: cumulative time 
    List reduced from 727 to 20 due to restriction 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     1 0.001 0.001 548.068 548.068 PlotD3PD_v3.2.py:2492(main) 
     4 0.000 0.000 346.756 86.689 /usr/lib//lib/python2.5/site-packages/guppy/heapy/Use.py:171(heap) 
     4 0.005 0.001 346.752 86.688 /usr/lib//lib/python2.5/site-packages/guppy/heapy/View.py:344(heap) 
     1 0.002 0.002 346.147 346.147 PlotD3PD_v3.2.py:2537(LogAndFinalize) 
     4 345.701 86.425 345.704 86.426 {gc.collect} 
     1 167.115 167.115 200.946 200.946 PlotD3PD_v3.2.py:2041(PlotBranches) 
     28 12.817 0.458 13.345 0.477 PlotROOTUtils.py:205(SaveItems) 
    9900 10.425 0.001 10.426 0.001 PlotD3PD_v3.2.py:1973(HistoStyle) 
    13202 0.336 0.000 6.818 0.001 PlotROOTUtils.py:431(PlottingCanvases) 
    6622 0.453 0.000 5.908 0.001 /root/svn_co/rbianchi/SoftwareDevelopment 

[...] 

>>> 

Ainsi, dans les deux sorties, triées par « temps » et par le temps « cumulatif » respectivement, gc.collect() est la fonction de consommer la plupart du temps de fonctionnement de mon programme!: -P

Et c'est la sortie du profileur de mémoire Heapy, juste avant de retourner le programme main().

 
memory usage before return: 
Partition of a set of 65901 objects. Total size = 4765572 bytes. 
Index Count %  Size % Cumulative % Kind (class/dict of class) 
    0 25437 39 1452444 30 1452444 30 str 
    1 6622 10 900592 19 2353036 49 dict of PlotROOTUtils.Canvas 
    2 109 0 567016 12 2920052 61 dict of module 
    3 7312 11 280644 6 3200696 67 tuple 
    4 6622 10 238392 5 3439088 72 0xa4ab74c 
    5 6622 10 185416 4 3624504 76 PlotROOTUtils.Canvas 
    6 2024 3 137632 3 3762136 79 types.CodeType 
    7 263 0 129080 3 3891216 82 dict (no owner) 
    8 254 0 119024 2 4010240 84 dict of type 
    9 254 0 109728 2 4119968 86 type 
    Index Count %  Size % Cumulative % Kind (class/dict of class) 
    10 1917 3 107352 2 4264012 88 function 
    11 3647 5 102116 2 4366128 90 ROOT.MethodProxy 
    12 148 0 80800 2 4446928 92 dict of class 
    13 1109 2 39924 1 4486852 93 __builtin__.wrapper_descriptor 
    14 239 0 23136 0 4509988 93 list 
    15  87 0 22968 0 4532956 94 dict of guppy.etc.Glue.Interface 
    16 644 1 20608 0 4553564 94 types.BuiltinFunctionType 
    17 495 1 19800 0 4573364 94 __builtin__.weakref 
    18  23 0 11960 0 4585324 95 dict of guppy.etc.Glue.Share 
    19 367 1 11744 0 4597068 95 __builtin__.method_descriptor 

Une idée pourquoi ou comment optimiser la garbage collection?

Y a-t-il des vérifications plus détaillées que je peux faire?

+9

"Des suggestions?". Utilisez le profileur pour obtenir plus d'informations sur l'heure de passage. Publiez les résultats en tant que mise à jour de votre question. –

+0

@nos: En fait, Python utilise refcounting, donc un objet non référencé * sera * collecté. Le GC de Python est plutôt simple comparé aux bêtes intelligentes dans les bonnes JVM et dans .NET. – delnan

+0

@delnan pour être précis, l'implémentation de CPython a ce comportement. J'ai l'impression de rappeler quelques versions expérimentales avec des algorithmes beaucoup plus sophistiqués. –

Répondre

7

This is known garbage collector issue in Python 2.6 occasionnant un temps quadratique pour la récupération de place lorsque de nombreux objets sont attribués sans désallouer l'un d'entre eux, c.-à-d. population de grande liste.
Il y a deux solutions simples:

  1. soit désactiver la collecte des ordures avant peuplant les grandes listes et lui permettent ensuite

    l = [] 
    gc.disable() 
    for x in xrange(10**6): 
        l.append(x) 
    gc.enable() 
    
  2. ou mise à jour Python 2.7, where the issue has been solved

Je pré pour la deuxième solution, mais ce n'est pas toujours une option;)

6

Oui, il pourrait s'agir d'une récupération de place, mais il pourrait aussi y avoir une certaine synchronisation avec le code C++, ou quelque chose de complètement différent (difficile à dire sans code).

De toute façon, vous devriez jeter un oeil à SIG for development of Python/C++ integration pour trouver des problèmes et comment accélérer les choses.

+0

Salut kriss, apparemment, il a un problème de garbage collector. J'ai mis à jour ma question avec la sortie de cProfile, et il semble que gc.collect prenne le plus de temps possible ... Je vais jeter un oeil à la SIG Python/C++ comme vous l'avez suggéré, pour voir si c'est un problème connu dans certains contextes. – rmbianchi

+0

raw bet: le gc est probablement dans un cas où refcounting ne fonctionne pas bien (doit utiliser une stratégie plus complexe pour récupérer de la mémoire). Des boucles dans votre structure de données (références cycliques)? Si c'est la source du problème, les couper «à la main» pourrait être une solution, cela rendrait gc plus heureux (donc plus rapide). – kriss

-3

Si votre problème est vraiment la garbage collection, essayez explicitement de libérer vos objets lorsque vous avez terminé avec eux en utilisant del().

En général, cela ne ressemble pas à un problème de récupération de place, sauf si nous parlons de téraoctets de mémoire.

Je suis d'accord avec S.Lott ... profil de votre application, puis apporter des extraits de code et les résultats de cela et nous pouvons être beaucoup plus utiles.

+2

'del' ne libère rien. Il supprime simplement une variable de la portée actuelle, c'est-à-dire supprime une référence. Mais Python est référencé (un GC plus sophistiqué existe et est exécuté, mais seulement sur des références cycliques) - peu importe si un tas d'objets est gc'd à la fin de la fonction ou en petits morceaux quand youthink vous c'est fait. – delnan

+0

En général, oui. Dans le cas pathologique, libérer de petits morceaux pourrait aider. Habituellement, utiliser 'del' partout tend à être un signe que vous ne programmez pas en Python. –

+0

Merci, Paul et Delnan. En fait, j'ai également essayé d'utiliser del(), mais dans ce contexte ne fonctionne pas. Comme je l'ai dit dans la mise à jour de ma question, j'utilise un framework open source appelé ROOT (http://root.cern.ch), avec son propre système de bindings python (appelé Reflex), et même si del() supprime toutes les références python à un objet C++, l'objet lui-même reste en mémoire ... Mais même si maintenant j'ai trouvé un moyen de les supprimer explicitement, la fonction gc.collect semble tirer le meilleur parti du temps de fonctionnement ... Des suggestions pour d'autres vérifications? Encore merci beaucoup pour votre aide – rmbianchi