2009-08-07 12 views
12

Sto riscontrando qualche problema con la memoria in Perl. Quando riempio un grosso hash, non riesco a riavere la memoria sul sistema operativo. Quando faccio lo stesso con uno scalare e uso undef, restituirà la memoria al sistema operativo.In Perl, come posso rilasciare memoria nel sistema operativo?

Ecco un programma di test che ho scritto.

#!/usr/bin/perl 
###### Memory test 
###### 

## Use Commands 
use Number::Bytes::Human qw(format_bytes); 
use Data::Dumper; 
use Devel::Size qw(size total_size); 

## Create Varable 
my $share_var; 
my %share_hash; 
my $type_hash = 1; 
my $type_scalar = 1; 

## Start Main Loop 
while (true) { 
    &Memory_Check(); 
    print "Hit Enter (add to memory): "; <>; 
    &Up_Mem(100_000); 
    &Memory_Check(); 

    print "Hit Enter (Set Varable to nothing): "; <>; 
    $share_var = ""; 
    $share_hash =(); 
    &Memory_Check(); 

    print "Hit Enter (clean data): "; <>; 
    &Clean_Data(); 
    &Memory_Check(); 

    print "Hit Enter (start over): "; <>; 
} 

exit; 


#### Up Memory 
sub Up_Mem { 
    my $total_loops = shift; 
    my $n = 1; 
    print "Adding data to shared varable $total_loops times\n"; 

    until ($n > $total_loops) { 
     if ($type_hash) { 
      $share_hash{$n} = 'X' x 1111; 
     } 
     if ($type_scalar) { 
      $share_var .= 'X' x 1111; 
     } 
     $n += 1; 
    } 
    print "Done Adding Data\n"; 
} 

#### Clean up Data 
sub Clean_Data { 
    print "Clean Up Data\n"; 

    if ($type_hash) { 
     ## Method to fix hash (Trying Everything i can think of! 
     my $n = 1; 
     my $total_loops = 100_000; 
     until ($n > $total_loops) { 
      undef $share_hash{$n}; 
      $n += 1; 
     } 

     %share_hash =(); 
     $share_hash =(); 
     undef $share_hash; 
     undef %share_hash; 
    } 
    if ($type_scalar) { 
     undef $share_var; 
    } 
} 

#### Check Memory Usage 
sub Memory_Check { 
    ## Get current memory from shell 
    my @mem = `ps aux | grep \"$$\"`; 
    my($results) = grep !/grep/, @mem; 

    ## Parse Data from Shell 
    chomp $results; 
    $results =~ s/^\w*\s*\d*\s*\d*\.\d*\s*\d*\.\d*\s*//g; $results =~ s/pts.*//g; 
    my ($vsz,$rss) = split(/\s+/,$results); 

    ## Format Numbers to Human Readable 
    my $h = Number::Bytes::Human->new(); 
    my $virt = $h->format($vsz); 
    my $h = Number::Bytes::Human->new(); 
    my $res = $h->format($rss); 

    print "Current Memory Usage: Virt: $virt RES: $res\n"; 

    if ($type_hash) { 
     my $total_size = total_size(\%share_hash); 
     my @arr_c = keys %share_hash; 
     print "Length of Hash: " . ($#arr_c + 1) . " Hash Mem Total Size: $total_size\n"; 
    } 
    if ($type_scalar) { 
     my $total_size = total_size($share_var); 
     print "Length of Scalar: " . length($share_var) . " Scalar Mem Total Size: $total_size\n"; 
    } 

} 

USCITA:

 
./Memory_Undef_Simple.cgi 
Current Memory Usage: Virt: 6.9K RES: 2.7K 
Length of Hash: 0 Hash Mem Total Size: 92 
Length of Scalar: 0 Scalar Mem Total Size: 12 
Hit Enter (add to memory): 
Adding data to shared varable 100000 times 
Done Adding Data 
Current Memory Usage: Virt: 228K RES: 224K 
Length of Hash: 100000 Hash Mem Total Size: 116813243 
Length of Scalar: 111100000 Scalar Mem Total Size: 111100028 
Hit Enter (Set Varable to nothing): 
Current Memory Usage: Virt: 228K RES: 224K 
Length of Hash: 100000 Hash Mem Total Size: 116813243 
Length of Scalar: 0 Scalar Mem Total Size: 111100028 
Hit Enter (clean data): 
Clean Up Data 
Current Memory Usage: Virt: 139K RES: 135K 
Length of Hash: 0 Hash Mem Total Size: 92 
Length of Scalar: 0 Scalar Mem Total Size: 24 
Hit Enter (start over): 

Così come si può vedere la memoria va giù, ma va solo in fondo alla dimensione del scalare. Qualche idea su come liberare la memoria dell'hash?

Anche Devel::Size mostra che l'hash occupa solo 92 byte anche se il programma utilizza ancora 139K.

+0

È necessario riformattare il post. È illeggibile. – EightyEight

+0

Posso assicurarti che Perl non sta usando solo 2.7K. ps riporta la memoria in blocchi da 1K, l'utilizzo della memoria è 1024 volte troppo basso. – Schwern

risposta

12

In generale, non è possibile aspettarsi che perl rilasci memoria nel sistema operativo.

Vedere le domande frequenti: How can I free an array or hash so my program shrinks?.

Di solito non è possibile. La memoria allocata ai lessicali (cioè le variabili my()) non può essere recuperata o riutilizzata anche se non rientra nell'ambito. È riservato nel caso in cui le variabili rientrino nel campo di applicazione. La memoria allocata alle variabili globali può essere riutilizzata (all'interno del programma) usando undef() e/o delete().

Sulla maggior parte dei sistemi operativi, la memoria allocata a un programma non può mai essere restituita al sistema. Ecco perché i programmi di lunga durata a volte si ricompensano. Alcuni sistemi operativi (in particolare i sistemi che utilizzano mmap(2) per allocare grandi blocchi di memoria) possono recuperare memoria che non è più utilizzata, ma su tali sistemi, perl deve essere configurato e compilato per utilizzare il malloc del sistema operativo, non perl.

È sempre una buona idea leggere lo FAQ list, installato anche sul computer, prima di perdere tempo.

Ad esempio, How can I make my Perl program take less memory? è probabilmente pertinente al problema.

+2

Il tuo link RTFF è molto buono. Indica che questo dipende dal sistema operativo. Se il sistema operativo lo supporta, è possibile liberare la memoria sul sistema operativo. Ho scritto un codice che fa esattamente ciò che l'OP desidera usando ActivePerl su WinXP. Non è necessaria l'ostilità extra, per favore considera di fissare il tuo primo paragrafo. – daotoad

+0

Ho disinnescato un po 'questa bomba offensiva. Abbiamo bisogno di persone come te che hanno un rep> 10k! Non correre rischi del genere, per favore. – innaM

+1

@daotoad e Manni Era un problema di temporizzazione. Quando l'ho scritto, il post originale era un disastro malformato e l'unica cosa che potevo discernere erano le primissime righe. Vedi anche il commento di EightEight sopra. Comunque, grazie per esserti preso cura di questo. –

19

In generale, sì, è così che funziona la gestione della memoria su UNIX. Se stai usando Linux con un glibc recente e stai usando quel malloc, puoi restituire memoria libera al sistema operativo. Non sono sicuro che Perl faccia questo, comunque.

Se si desidera lavorare con grandi serie di dati, non caricare il tutto in memoria, usare qualcosa come BerkeleyDB: codice

https://metacpan.org/pod/BerkeleyDB

Esempio, rubato testualmente:

use strict ; 
    use BerkeleyDB ; 

    my $filename = "fruit" ; 
    unlink $filename ; 
    tie my %h, "BerkeleyDB::Hash", 
       -Filename => $filename, 
       -Flags => DB_CREATE 
     or die "Cannot open file $filename: $! $BerkeleyDB::Error\n" ; 

    # Add a few key/value pairs to the file 
    $h{apple} = "red" ; 
    $h{orange} = "orange" ; 
    $h{banana} = "yellow" ; 
    $h{tomato} = "red" ; 

    # Check for existence of a key 
    print "Banana Exists\n\n" if $h{banana} ; 

    # Delete a key/value pair. 
    delete $h{apple} ; 

    # print the contents of the file 
    while (my ($k, $v) = each %h) 
    { print "$k -> $v\n" } 

    untie %h ; 

(OK, non testualmente. Il loro uso di use vars è ... legacy ...)

È possibile archiviare gigabyte di dati in un hash in questo modo, e y o userai solo un po 'di memoria. (Fondamentalmente, qualunque cercapersone di BDB decide di tenere in memoria, questo è controllabile.)

+1

+1 Ottima dimostrazione del consiglio dato nell'ultima parte della risposta FAQ: http://faq.perl.org/perlfaq3.html#How_can_I_make_my_Pe1 –

+3

Le FAQ sono sbagliate sulle prestazioni, in genere si colpisce la cache e questo non è più costoso (in termini di tempo) rispetto all'accesso alla struttura in memoria. (E una volta avviato lo swapping, le strutture in memoria sono orripilantemente lente, poiché gli hash non hanno una buona localizzazione di riferimento. Ricordo di aver scritto alcuni script ETL che eseguivano più ordini di grandezza più velocemente con hash BDB anziché hash nativi.) – jrockway

+0

@ jrockway Immagino che la penalizzazione delle prestazioni abbia importanza solo quando non ti preoccupi dell'utilizzo della memoria: piccole strutture dati che si adattano completamente alla memoria su una macchina caricata leggermente. –

8

Perché vuoi che Perl rilasci la memoria sul sistema operativo? Potresti semplicemente usare uno scambio più grande.

Se proprio devi, esegui il tuo lavoro in un processo a forcella, quindi esci.

+4

Questa risposta non merita un downvote. Un processo biforcuto è un modo perfettamente ragionevole per gestire picchi temporali ben definiti nell'uso della memoria nei programmi a lunga esecuzione. –

+0

Il problema è che il server ha 3 GB di ram. 1 GB per sistema operativo e 1 GB per MySQL. Il mio processo inizierà da 27mb e arriverà a circa 800mb. Quindi il sistema inizierà a passare allo swap e rallenterà tutto. Il problema con la forcella è che copierà tutti gli 800mb nel nuovo processo. – clintonm9

+0

Inoltre, per aggiungere altro a questo; Sto usando thread diversi per fare cose diverse asincrone. utilizzare thread; usa threads :: shared; usa Thread :: Queue; Quindi passerò i dati a un hash condiviso, quindi l'altro thread elaborerà i dati così come arrivano. Questo è passato a molti thread che fanno cose diverse. A un certo punto, suppongo che l'hash stia diventando molto grande e abbia preso molta memoria. Forse c'è un modo migliore di farlo in generale? Il mio problema con il processo di fork diverso è che sembra molto più difficile passare i dati avanti e indietro. Qualche idea? – clintonm9

0

Provare a ricompilare perl con l'opzione -Uusemymalloc per utilizzare il sistema malloc e gratuito. Potresti vedere alcuni risultati diversi

Problemi correlati