2009-08-31 12 views
33

Ho scritto un servizio di rete persistente in Perl che gira su Linux.Perl profiling dell'utilizzo della memoria e rilevamento delle perdite?

Sfortunatamente, mentre è in esecuzione, il Resident Stack Size (RSS) cresce, cresce e cresce lentamente ma inesorabilmente.

Questo nonostante gli sforzi diligenti da parte mia di eliminare tutte le chiavi di hash non necessarie ed eliminare tutti i riferimenti a oggetti che altrimenti causerebbero il conteggio dei riferimenti e impedire la raccolta dei dati inutili.

Esistono buoni strumenti per profilare l'utilizzo della memoria associato a varie primitive di dati nativi, oggetti di riferimento hash benedetti, ecc. All'interno di un programma Perl? Cosa usi per rintracciare le perdite di memoria?

Non trascorro abitualmente il tempo nel debugger Perl o in nessuno dei vari profiler interattivi, quindi sarebbe gradita una risposta calda, gentile, non esoterica. :-)

+0

Hai capito? La mia ipotesi migliore data le informazioni che hai fornito è che c'è una libreria (introdotta tramite il dynaloader di alcuni moduli) che è il colpevole ... – Ether

+0

Questa sembra essere diventata la "ricerca di una perdita di memoria" canonica, dato che le mie risposte dagli altri domande simili sono state tutte unite qui :) In realtà non ho risposto a una domanda tre volte; più thread sono stati uniti nel tempo. – Ether

+0

Slip of tongue ... intendevi "Resident Set Size" ... questo numero non è correlato allo stack –

risposta

13

Si potrebbe avere un riferimento circolare in uno dei propri oggetti. Quando il garbage collector arriva per deallocare questo oggetto, il riferimento circolare significa che tutto ciò a cui si riferisce quel riferimento non verrà mai liberato. È possibile verificare i riferimenti circolari con Devel::Cycle e Test::Memory::Cycle. Una cosa da provare (anche se potrebbe ottenere costosi nel codice di produzione, quindi mi piacerebbe disattivarlo quando un flag di debug non è impostato) sta controllando i riferimenti circolari all'interno del distruttore per tutti gli oggetti:

# make this be the parent class for all objects you want to check; 
# or alternatively, stuff this into the UNIVERSAL class's destructor 
package My::Parent; 
use strict; 
use warnings; 
use Devel::Cycle; # exports find_cycle() by default 

sub DESTROY 
{ 
    my $this = shift; 

    # callback will be called for every cycle found 
    find_cycle($this, sub { 
      my $path = shift; 
      foreach (@$path) 
      { 
       my ($type,$index,$ref,$value) = @$_; 
       print STDERR "Circular reference found while destroying object of type " . 
        ref($this) . "! reftype: $type\n"; 
       # print other diagnostics if needed; see docs for find_cycle() 
      } 
     }); 

    # perhaps add code to weaken any circular references found, 
    # so that destructor can Do The Right Thing 
} 
+1

PS. È possibile indebolire un riferimento esistente (per consentire a un distruttore di eseguire la sua magia attraverso i cicli) con Scalar :: Util :: weaken() - http://search.cpan.org/~gbarr/Scalar-List-Utils-1.21 /lib/Scalar/Util.pm – Ether

+2

Hi Ether - ha provato l'UNIVERSAL :: DESTROY(), ha eseguito il servizio per un bel po 'di tempo e ci ha colpito sopra, e non ha ottenuto nulla. Nel frattempo, l'RSS stava strisciando. Cos'altro potrebbe essere se non fosse una questione di riferimenti circolari? –

+2

Non sono davvero sicuro che cercare perdite di memoria in un distruttore possa farti qualche favore. In caso di perdite, DESTROY non verrà mai chiamato. –

9

Puoi utilizzare Devel::Leak per cercare perdite di memoria. Tuttavia, la documentazione è piuttosto scarsa ... per esempio, solo dove si ottiene il riferimento $ handle per passare a Devel::Leak::NoteSV()? f Trovo la risposta, modifico questa risposta.

Ok si scopre che l'utilizzo di questo modulo è abbastanza semplice (codice rubato spudoratamente a Apache::Leak):

use Devel::Leak; 

my $handle; # apparently this doesn't need to be anything at all 
my $leaveCount = 0; 
my $enterCount = Devel::Leak::NoteSV($handle); 
print STDERR "ENTER: $enterCount SVs\n"; 

# ... code that may leak 

$leaveCount = Devel::Leak::CheckSV($handle); 
print STDERR "\nLEAVE: $leaveCount SVs\n"; 

avevo posto come più codice possibile nella sezione centrale, con il controllo leaveCount come vicino alla fine dell'esecuzione (se ne hai uno) il più possibile - dopo che molte variabili sono state deallocate il più possibile (se non puoi ottenere una variabile fuori dall'ambito, puoi assegnarle undef per liberare tutto ciò che stava puntando a).

+0

sono utilizzate in modo incoerente nell'esempio, ad es. $ lasciare vs $ leaveCount – Lot105

4

Quale futuro per provare (non so se questo sarebbe nella posizione migliore in un commento dopo la domanda di Alex sopra però): Quello che mi piacerebbe provare la prossima (diverso da Devel :: Leak):

cercare di eliminare " inutili "parti del tuo programma, o segmentarlo in file eseguibili separati (potrebbero usare segnali per comunicare, o chiamarsi reciprocamente con argomenti della riga di comando) - l'obiettivo è di ridurre un eseguibile nella più piccola quantità di codice che mostra ancora il cattivo comportamento. Se sei sicuro che non è il tuo codice a farlo, riduci il numero di moduli esterni che stai utilizzando, in particolare quelli che hanno un'implementazione XS. Se forse è il proprio codice, cercare qualcosa di potenzialmente pesce:

  • definitivamente qualsiasi utilizzo di Inline :: C o XS codice
  • uso diretto di riferimenti, per esempio\@list o \%hash, piuttosto che riferimenti preallocati come [qw (foo bar)] (il primo crea un altro riferimento che può andare perduto, nel secondo c'è solo un riferimento di cui preoccuparsi, che di solito è memorizzato in uno scalare lessicale locale
  • variabili manipolare indirettamente, ad esempio $$foo dove $foo viene modificato, che può causare autovivication di variabili (anche se è necessario disabilitare strict 'refs' controllo)
2

Recentemente ho usato NYTProf come profiler per una grande applicazione Perl. Non tiene traccia dell'utilizzo della memoria, ma traccia tutti i percorsi di codice eseguiti che aiutano a scoprire da dove provengono le perdite. Se ciò che si perde sono risorse scarse come le connessioni al database, tracciare dove sono allocate e chiuse fa molto per trovare le perdite.