2012-10-05 19 views
7

Questa domanda è un punto di curiosità, poiché uno dei due programmi di seguito funziona.Garbage collection in Perl threads

Uso Image: Magick per ridimensionare un numero di foto. Per risparmiare un po 'di tempo, lavoro su ogni foto con il suo thread e uso un semaforo per limitare il numero di thread che funzionano simultaneamente. Inizialmente, consentivo a tutti i thread di essere eseguiti contemporaneamente, ma lo script assegnava rapidamente 3,5 GB per tutte le foto (ne avevo solo 2 GB disponibili) e lo script avrebbe eseguito 5 volte più lentamente del normale a causa di tutto lo swapping su disco.

La lavorazione, semaforo codice di versione sembra qualcosa di simile:

use threads; 
use Thread::Semaphore; 
use Image::Magick; 

my $s = Thread::Semaphore->new(4); 
foreach (@photos) { 
    threads->create(\&launch_thread, $s); 
} 
foreach my $thr (reverse threads->list()) { 
    $thr->join(); 
} 

sub launch_thread { 
    my $s = shift; 
    $s->down(); 
    my $image = Image::Magick->new(); 

    # do memory-heavy work here 

    $s->up(); 
} 

Questo assegna rapidamente 500MB, e corre molto bene, senza mai che richiede più. (I fili vengono uniti in ordine inverso per fare un punto.)

ho chiesto se ci potrebbe essere sovraccarico di lanciare 80 thread contemporaneamente e bloccando la maggior parte di esse, così modificati mio script per bloccare il filo principale:

my $s = Thread::Semaphore->new(4); 
foreach (@photos) { 
    $s->down(); 
    threads->create(\&launch_thread, $s); 
} 
foreach my $thr (threads->list()) { 
    $thr->join(); 
} 

sub launch_thread { 
    my $s = shift; 
    my $image = Image::Magick->new(); 

    # do memory-heavy work here 

    $s->up(); 
} 

Questa versione inizia bene, ma gradualmente accumula i 3,5 GB di spazio utilizzato dalla versione originale. È più veloce di eseguire tutti i thread contemporaneamente, ma è ancora un po 'più lento del blocco dei thread.

La mia prima ipotesi è che la memoria utilizzata da un thread non venga liberata fino a quando join() non viene chiamato su di esso, e poiché è il thread principale che blocca, nessun thread viene liberato fino a quando non sono stati tutti allocato. Tuttavia, nella prima versione funzionante, i thread passano la guardia in un ordine casuale più o meno, ma si uniscono in ordine inverso. Se la mia ipotesi è corretta, quindi, molti più dei quattro thread in esecuzione dovrebbero essere in attesa di essere join() in qualsiasi momento, e anche questa versione dovrebbe essere più lenta.

Allora perché queste due versioni sono così diverse?

risposta

3

Non è necessario creare più di 4 thread. Uno dei principali vantaggi è che questo significa 76 meno copie dell'interprete Perl. Inoltre, rende l'ordine di raccolta piuttosto discutibile poiché tutti i thread finiscono più o meno nello stesso tempo.

use threads; 
use Thread::Queue qw(); 
use Image::Magick qw(); 

use constant NUM_WORKERS => 4; 

sub process { 
    my ($photo) = @_; 
    ... 
} 

{ 
    my $request_q = Thread::Queue->new(); 

    my @threads; 
    for (1..NUM_WORKERS) { 
     push @threads, async { 
      while (my $photo = $request_q->dequeue()) { 
      process($photo); 
      } 
     }; 
    } 

    $request_q->enqueue($_) for @photos; 
    $request_q->enqueue(undef) for 1..NUM_THREADS; 
    $_->join() for @threads; 
} 
+0

Stavo per provare una coda dopo. Sono solo curioso di sapere cosa sta succedendo in Perl che fa funzionare perfettamente una versione del semaforo, e uno funziona terribilmente. – pconley

+0

Nella tua versione, solo i thread che hanno sbloccato il sem usano molta memoria. Se li raccogli mentre completano, significa che solo 4 thread stanno usando molta memoria in un dato momento. Se li raccogli solo alla fine, 80 thread alla fine usano molta memoria. – ikegami