2012-03-16 9 views
8

Voglio creare e manipolare grandi matrici di (4 byte) numeri interi in memoria. Per grandi, intendo nell'ordine di centinaia di milioni. Ogni cella dell'array agirà da contatore per una posizione su un cromosoma. Tutto ciò di cui ho bisogno è che si adatti alla memoria e che abbia accesso veloce (O (1)) agli elementi. La cosa che sto contando non è una funzione sparsa, quindi non posso usare un array sparse.Matrici di tipo C in perl

Non riesco a farlo con una normale lista perl, perché perl (almeno sulla mia macchina) usa 64 byte per elemento, quindi i genomi della maggior parte degli organismi con cui lavoro sono troppo grandi. Ho provato a memorizzare i dati su disco tramite SQLite e hash, e sebbene funzionino, sono molto lenti, specialmente su dischi ordinari. (Funziona ragionevolmente bene quando corro su 4 RAID raid 0).

Pensavo di poter utilizzare gli array PDL, b/c PDL memorizza gli array come fa C, utilizzando solo 4 byte per elemento. Tuttavia, ho trovato che la velocità di aggiornamento per essere estremamente lento rispetto a liste perl:

use PDL; 
use Benchmark qw/cmpthese/; 

my $N = 1_000_000; 
my @perl = (0 .. $N - 1); 
my $pdl = zeroes $N; 

cmpthese(-1,{ 
    perl => sub{ 
     $perl[int(rand($N))]++; 
    }, 
    pdl => sub{ 
     # note that I'm not even incrementing here just setting to 1 
     $pdl->set(int(rand($N)), 1); 
    } 
}); 

Ritorni:

  Rate pdl perl 
pdl 481208/s -- -87% 
perl 3640889/s 657% --  

qualcuno sa come aumentare set pdl() le prestazioni, o sa di un modulo diverso quello può realizzare questo?

+1

hai guardato i moduli perl bioinformatici? – AlfredoVR

risposta

8

Non riesco a stabilire quale tipo di prestazioni si otterrà, ma si consiglia di utilizzare la funzione vec, documentata here, per dividere una stringa in campi di bit. Ho sperimentato e ho scoperto che il mio Perl tollererebbe una stringa fino a 500_000_000 caratteri. che corrisponde a 125.000.000 di valori a 32 bit.

my $data = "\0" x 500_000_000; 
vec($data, 0, 32)++;   # Increment data[0] 
vec($data, 100_000_000, 32)++; # Increment data[100_000_000] 

Se questo non è sufficiente ci può essere qualcosa nella build del Perl che controlla il limite. In alternativa, se si pensa di poter ottenere i campi più piccoli - dice conta 16 bit - vec accetterà larghezze di campo di qualsiasi potenza di 2 fino a 32.

Edit: Credo che il limite di dimensione della stringa è correlato al massimo 2GB lavoro privato impostato su processi Windows a 32 bit. Se stai usando Linux o hai un perl 64 bit potresti essere più fortunato di me.


ho aggiunto al tuo programma di benchmark come questo

my $vec = "\0" x ($N * 4); 

cmpthese(-3,{ 
    perl => sub{ 
     $perl[int(rand($N))]++; 
    }, 
    pdl => sub{ 
     # note that I'm not even incrementing here just setting to 1 
     $pdl->set(int(rand($N)), 1); 
    }, 
    vec => sub { 
     vec($vec, int(rand($N)), 32)++; 
    }, 
}); 

dando questi risultati

  Rate pdl vec perl 
pdl 472429/s -- -76% -85% 
vec 1993101/s 322% -- -37% 
perl 3157570/s 568% 58% -- 

in modo da utilizzare vec è di due terzi la velocità di una matrice nativa. Presumibilmente è accettabile.

2

PDL vince quando è possibile eseguire il thread delle operazioni, apparentemente non ottimizzato per l'accesso casuale e l'assegnazione. Forse qualcuno con più conoscenza di PDL potrebbe aiutare.

+0

Non è l'accesso casuale ma il tempo di avvio per inizializzare le strutture di dati PDL. A seconda del calcolo in questione, PDL richiede che vengano elaborati da 100 a 1000 elementi prima di interrompere anche le prestazioni con operazioni native perl. Con il suo supporto per il loop implicito, una riga di codice PDL può sostituire più scalari nidificati o scalari nidificati in plain perl. – chm

+0

Saresti il ​​migliore a saperlo, grazie per il commento. Certamente ci sono risposte migliori pubblicate su questa domanda ora della mia. :-) –

2

Packed::Array su CPAN potrebbe aiutare.

Packed :: Array fornisce una classe di matrice di interi integer firmati. Gli array creati usando Packed :: Array possono contenere solo interi con segno che corrispondono agli interi nativi della piattaforma, ma richiedono solo la quantità di memoria necessaria per contenere tali numeri interi. Quindi, per i sistemi a 32 bit, anziché prendere circa 20 byte per ogni voce di array, ne prendono solo 4.

+2

Sì, ho esaminato questo modulo, oltre a 'Tie :: Array :: Pack',' Tie :: Array :: Packed', 'Tie :: VecArray' e' Tie :: Array :: PackedC '. Sfortunatamente l'API Perl 'tie' è molto lenta e il più veloce (' Tie :: Array :: Packed') è ancora dieci volte più lento degli array nativi. – Borodin

7

Il comando PDL desiderato è indadd. (Grazie a Chris Marshall, PDL Pumpking, per avermelo fatto notare elsewhere.)

PDL è progettato per quelle che io chiamo operazioni "vettorializzate". Rispetto alle operazioni C, le operazioni di Perl sono piuttosto lente, quindi è necessario mantenere il numero di invocazioni del metodo PDL al minimo e fare in modo che ciascuna invocazione faccia molto lavoro. Ad esempio, questo benchmark consente di specificare il numero di aggiornamenti da eseguire in un unico passaggio (come parametro della riga di comando). Il lato perl deve loop, ma il lato Pdl esegue solo cinque o giù di chiamate di funzione:

use PDL; 
use Benchmark qw/cmpthese/; 

my $updates_per_round = shift || 1; 

my $N = 1_000_000; 
my @perl = (0 .. $N - 1); 
my $pdl = zeroes $N; 

cmpthese(-1,{ 
    perl => sub{ 
     $perl[int(rand($N))]++ for (1..$updates_per_round); 
    }, 
    pdl => sub{ 
     my $to_update = long(random($updates_per_round) * $N); 
     indadd(1,$to_update,$pdl); 
    } 
}); 

Quando eseguo questo con un argomento di 1, ottengo prestazioni ancora peggio di quando si utilizza set, che è quello che ho previsto:

$ perl script.pl 1 
      Rate pdl perl 
pdl 21354/s -- -98% 
perl 1061925/s 4873% -- 

Questo è un sacco di terreno da inventare! Ma aspetta lì. Se facciamo 100 iterazioni per ogni turno, si ottiene un miglioramento:

$ perl script.pl 100 
     Rate pdl perl 
pdl 16906/s -- -18% 
perl 20577/s 22% -- 

E con 10.000 aggiornamenti per ogni turno, Pdl sorpassa Perl di un fattore di quattro:

$ perl script.pl 10000 
     Rate perl pdl 
perl 221/s -- -75% 
pdl 881/s 298% -- 

PDL continua a svolgere circa 4 volte più veloce del semplice Perl per valori ancora più grandi.

Si noti che le prestazioni del PDL possono peggiorare per operazioni più complesse. Questo perché PDL alloca e abbatte spazi di lavoro ampi ma temporanei per le operazioni intermedie. In tal caso, potresti prendere in considerazione l'utilizzo di Inline::Pdlpp. Tuttavia, non è uno strumento per principianti, quindi non saltare lì finché non hai determinato che è davvero la cosa migliore per te.

Un'altra alternativa a tutto questo è quello di utilizzare Inline::C in questo modo:

use PDL; 
use Benchmark qw/cmpthese/; 

my $updates_per_round = shift || 1; 

my $N = 1_000_000; 
my @perl = (0 .. $N - 1); 
my $pdl = zeroes $N; 
my $inline = pack "d*", @perl; 
my $max_PDL_per_round = 5_000; 

use Inline 'C'; 

cmpthese(-1,{ 
    perl => sub{ 
     $perl[int(rand($N))]++ for (1..$updates_per_round); 
    }, 
    pdl => sub{ 
     my $to_update = long(random($updates_per_round) * $N); 
     indadd(1,$to_update,$pdl); 
    }, 
    inline => sub{ 
     do_inline($inline, $updates_per_round, $N); 
    }, 
}); 


__END__ 

__C__ 

void do_inline(char * packed_data, int N_updates, int N_data) { 
    double * actual_data = (double *) packed_data; 
    int i; 
    for (i = 0; i < N_updates; i++) { 
     int index = rand() % N_data; 
     actual_data[index]++; 
    } 
} 

Per me, la funzione Inline batte costantemente sia Perl e Pdl. Per i valori più ampi di $updates_per_round, ad esempio 1.000, ottengo la versione di Inline::C circa 5 volte più veloce del Perl puro e tra 1,2x e 2 volte più veloce di PDL. Anche quando $updates_per_round è solo 1, dove Perl batte maneggevolmente PDL, il codice Inline è 2,5 volte più veloce del codice Perl.

Se questo è tutto il è necessario eseguire, mi consiglia di utilizzare Inline::C.

Ma se è necessario eseguire molte manipolazioni sui dati, è meglio attenersi al PDL per potenza, flessibilità e prestazioni. Vedi sotto come utilizzare vec() con dati PDL.

4

PDL::set() e PDL::get() sono più che altro un aiuto all'apprendimento. Costituiscono il modo pessimistico per accedere alle variabili PDL. Sarebbe molto meglio usare alcune delle routine di accesso in blocco integrate. Lo stesso costruttore PDL accetta liste Perl:

$pdl = pdl(@list) 

ed è ragionevolmente veloce. È inoltre possibile caricare i dati direttamente da un file ASCII utilizzando PDL::rcols o da un file binario utilizzando una delle numerose routine IO.Se si dispone i dati come una stringa confezionato in ordine la macchina, si può anche ottenere l'accesso alla memoria PDL direttamente:

$pdl = PDL->new_from_specification(long,$elements); 
$dr = $pdl->get_dataref; 
$$dr = get_my_packed_string(); 
$pdl->upd_data; 

Si noti inoltre che è possibile "avere la botte piena e la moglie ubriaca" utilizzando oggetti PDL a tenere gli array di interi, calcoli PDL (come ad esempio indadd) per la manipolazione su larga scala dei dati, ma anche utilizzare vec() direttamente sui dati PDL come una stringa che si può ottenere attraverso il metodo get_dataref:

vec($$dr,int(rand($N)),32); 

Avrai bisogno di bswap4 i dati se sei su un sistema little-endian:

$pdl->bswap4; 
$dr = $pdl->get_dataref; 
vec($$dr,int(rand($N)),32)++; 
$pdl->upd_data; 
$pdl->bswap4; 

Et, voilà!

2

poiché stavano usando numeri interi, che dovrebbe essere ok per l'uso con cromosomi provare questo

use PDL; 
use Benchmark qw/cmpthese/; 

my $N = 1_000_000; 
my @perl; 
@perl = (0 .. $N - 1); 
my $pdl; 
$pdl = (zeroes($N)); 

cmpthese(-1,{ 
perl => sub{ 
    $perl[int(rand($N))]++; 
}, 
pdl2 => sub{ 
    # note that I'm not even incrementing here just setting to 1 
    $pdl->set(int(rand($N)), 1); 
    $pdl2 = pack "w*", $pdl; 
} 
}); 

e mettere fuori ho avuto da questo è stato ...

  Rate pdl2 perl 
pdl2 46993/s -- -97% 
perl 1641607/s 3393% -- 

che mostra una grande differenza di prestazioni da quando ho provato questo codice con fuori aggiungendo nei miei 2 centesimi ho ottenuto

  Rate pdl perl 
pdl 201972/s -- -86% 
perl 1472123/s 629% -- 
0

La mia risposta di cui sopra può essere inutile ... questo potrebbe aiutare tho ..

use PDL; 
$x = sequence(45000,45000); 

ora che non funzionerà se non si dispone di 16 GB di RAM e utilizzare

$PDL::BIGPDL=1;