2010-10-12 26 views
15

Ho un problema con la mia applicazione python, e penso che sia correlato alla garbage collection di Python, anche se non sono sicuro ...La garbage collection di Python può essere così lenta?

Il problema è che la mia applicazione impiega molto tempo per uscire e per passare a una funzione alla successiva.

Nella mia applicazione gestisco dizionari molto grandi, contenenti migliaia di oggetti di grandi dimensioni che vengono istanziati da classi C++ avvolte.

Ho inserito alcune uscite di timestamp nel mio programma, e ho visto che alla fine di ogni funzione, quando gli oggetti creati all'interno della funzione dovevano andare fuori campo, l'interprete impiega molto tempo prima di chiamare la funzione successiva . E osservo lo stesso problema alla fine dell'applicazione, quando il programma dovrebbe uscire: un sacco di tempo (~ ore!) È trascorso tra l'ultimo timestamp sullo schermo e l'aspetto del nuovo prompt.

L'utilizzo della memoria è stabile, quindi non ho perdite di memoria.

Qualche suggerimento?

Può essere la garbage collection di migliaia di oggetti C++ di grandi dimensioni che rallentano?

C'è un metodo per accelerare?

UPDATE:

Grazie mille per tutte le vostre risposte, mi ha dato un sacco di suggerimenti per eseguire il debug il mio codice :-)

io uso Python 2.6.5 su Scientific Linux 5, un distribuzione personalizzata basata su Red Hat Enterprise 5. E in realtà non sto usando SWIG per ottenere i binding Python per il nostro codice C++, ma il framework Reflex/PyROOT. Lo so, non è molto conosciuto al di fuori della fisica delle particelle (ma è ancora open source e disponibile gratuitamente) e devo usarlo perché è l'impostazione predefinita per il nostro framework principale.

E in questo contesto il comando DEL dal lato Python non funziona, l'avevo già provato. DEL elimina solo la variabile python collegata all'oggetto C++, non l'oggetto stesso in memoria, che è ancora di proprietà del lato C++ ...

... Lo so, non è standard, credo, e un po ' complicato, mi spiace :-P

Ma seguendo i tuoi suggerimenti, descriverò il mio codice e tornerò da te con ulteriori dettagli, come hai suggerito.

ULTERIORI UPDATE:

Ok, seguendo i vostri suggerimenti, ho il mio codice strumentato con cProfile, e ho scoperto che in realtà la funzione gc.collect() è la funzione prendendo la maggior parte del tempo in esecuzione !!

Qui l'uscita dal 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 

[...] 

>>> 

Quindi, in entrambe le uscite, scelte per "tempo" e da tempo "cumulativo", rispettivamente, gc.collect() è la funzione che consumano la maggior parte del tempo di esecuzione del mio programma!:-P

E questa è l'uscita del profiler di memoria Heapy, appena prima di restituire il programma 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 

Qualche idea sul perché o su come ottimizzare la garbage collection?

C'è qualche controllo più dettagliato che posso fare?

+9

"Qualche suggerimento?". Usa il profiler per ottenere più informazioni su dove viene speso il tempo. Pubblica i risultati come aggiornamento della tua domanda. –

+0

@nos: In realtà, Python usa il refcounting, quindi verrà raccolto un oggetto * non referenziato. Il GC di Python è piuttosto semplice rispetto alle bestie intelligenti nelle buone JVM e in .NET. – delnan

+0

@delnan per la precisione, l'implementazione di CPython ha quel comportamento. Mi sembra di ricordare alcune versioni sperimentali con algoritmi molto più elaborati. –

risposta

7

This is known garbage collector issue in Python 2.6 causa il tempo quadratico per la garbage collection quando molti oggetti vengono allocati senza deallocare nessuno di essi, ad esempio. popolazione di grande lista.
Ci sono due soluzioni semplici:

  1. disabilitare la raccolta dei rifiuti prima di occupare grandi liste e gli permettono poi

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

Io pre per la seconda soluzione, ma non è sempre un'opzione;)

6

Sì, potrebbe essere la garbage collection, ma potrebbe anche essere una sincronizzazione con il codice C++ o qualcosa di completamente diverso (difficile da dire senza codice).

In ogni caso, si dovrebbe dare un'occhiata a SIG for development of Python/C++ integration per trovare problemi e come accelerare le cose.

+0

Ciao Kris, a quanto pare ha un problema di garbage collector. Ho aggiornato la mia domanda con l'output di cProfile, e sembra che gc.collect prenda il massimo del tempo di esecuzione ... Dare un'occhiata al SIG Python/C++ come hai suggerito, per vedere se è un problema noto in alcuni contesti. – rmbianchi

+0

scommessa cruda: il gc è probabilmente in un caso in cui il conteggio non funziona bene (deve usare una strategia più complessa per recuperare la memoria). Eventuali loop nella struttura dei dati (riferimenti ciclici)? Se è la fonte del problema tagliarli "a mano" potrebbe essere una soluzione, renderebbe gc più felice (quindi più veloce). – kriss

-3

Se il problema è in realtà la raccolta dati inutili, prova a liberare esplicitamente gli oggetti quando hai finito con loro utilizzando del().

In generale, questo non sembra un problema di garbage collection, a meno che non stiamo parlando di terabyte di memoria.

Sono d'accordo con S.Lott ... profilo la tua app, quindi portare frammenti di codice e i risultati di quello posteriore e possiamo essere molto più utile.

+2

'del' non libera nulla. Rimuove solo una variabile dall'ambito corrente, cioè rimuove un riferimento. Ma Python viene conteggiato (un GC più sofisticato esiste ed è in esecuzione, ma solo su riferimenti ciclici) - non importa se un gruppo di oggetti ottengono gc alla fine della funzione o in piccoli pezzi quando si è giovani fatto. – delnan

+0

In generale, si. Nel caso patologico, aiutare a liberare piccoli pezzi. Usando abitualmente 'del' dappertutto si tende a essere un segno che non si sta programmando in Python. –

+0

Grazie, Paul e Delnan. In realtà ho anche provato a usare del(), ma in questo contesto non funziona. Come ho detto nell'aggiornamento della mia domanda, sto usando un framework open source chiamato ROOT (http://root.cern.ch), con un proprio sistema di collegamenti Python (chiamato Reflex), e anche se del() cancella tutti i riferimenti Python a un oggetto C++, l'oggetto stesso rimane in memoria ... Ma anche se ora ho trovato un modo per eliminarli esplicitamente, la funzione gc.collect sembra sfruttare al massimo il tempo di esecuzione ... Qualche suggerimento per ulteriori controlli? Grazie ancora per il tuo aiuto – rmbianchi