La mia risposta istantanea sarebbe stata awk
ma se stai elaborando molte linee - e sto parlando di milioni - probabilmente vedrai un vero vantaggio dal passaggio a un "vero" linguaggio di programmazione.
Con questo in mente (e awk
già considerato come una risposta) ho scritto alcune implementazioni in diverse lingue e le ho confrontate sullo stesso set di dati da 10.000 righe su un SSD PCI-E.
me* (C) 0m1.734s
me (C++) 0m1.991s
me (Python/Pypy) 0m2.390s
me (perl) 0m3.024s
Thor+Glenn (sed|sh) 0m3.353s
me (python) 0m3.359s
jasonwryan+Thor (awk) 0m3.779s
rush (while read) 0m6.011s
Thor (sed) 1m30.947s
me (parallel) 4m9.429s
A prima vista, la C sembra la migliore, ma era un maiale correre così veloce. Pypy e C ++ sono molto più facili da scrivere ed eseguire abbastanza bene a meno che tu non stia parlando di miliardi di righe. Se così fosse, un aggiornamento per fare tutto ciò in RAM o su un SSD potrebbe essere un investimento migliore di un miglioramento del codice.
Ovviamente nel tempo che ho passato attraverso questi, probabilmente avresti potuto elaborare alcune centinaia di milioni di dischi nell'opzione più lenta . Se riesci solo a scrivere awk
o creare loop di Bash, fallo e vai avanti con la vita. Ho avuto chiaramente troppo tempo libero oggi.
Ho anche testato alcune opzioni multi-thread (in C ++ e Python e ibridi con GNU parallel
) ma l'overhead dei thread supera completamente qualsiasi vantaggio per un'operazione così semplice (suddivisione delle stringhe, scrittura).
Perl
awk
( gawk
qui) sarebbe sinceramente il mio primo punto di riferimento per testare dati come questo, ma puoi fare cose abbastanza simili in Perl. Sintassi simile ma con una maniglia di scrittura leggermente migliore.
perl -ane 'open(my $fh, ">", $F[0].".seq"); print $fh $F[1]; close $fh;' infile
Pitone
Mi piace Python. È la mia lingua del lavoro quotidiano ed è solo una lingua piacevole, solida e incredibilmente leggibile. Anche un principiante potrebbe probabilmente indovinare cosa sta succedendo qui.
with open("infile", "r") as f:
for line in f:
id, chunk = line.split()
with open(id + ".seq", "w") as fw:
fw.write(chunk)
Devi ricordare che il python
binario della tua distribuzione non è l'unica implementazione di Python là fuori. Quando ho eseguito lo stesso test tramite Pypy, è stato più veloce di C senza ulteriori ottimizzazioni logiche. Tienilo a mente prima di scrivere Python come "linguaggio lento".
C
Ho iniziato questo esempio per vedere cosa potremmo davvero fare per far funzionare la mia CPU ma, francamente, C è un incubo da programmare se non lo tocchi da molto tempo. Questo ha il rovescio della medaglia di essere limitato a linee da 100 caratteri anche se è molto semplice espanderlo, non ne avevo proprio bisogno.
La mia versione originale era più lenta di C ++ e pypy, ma dopo aver scritto sul blog ho ricevuto un aiuto da Julian Klode . Questa versione è ora la più veloce a causa dei suoi buffer IO ottimizzati. È anche molto più lungo e coinvolto più di ogni altra cosa.
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#define BUFLEN (8 * 1024)
int main(void) {
FILE *fp;
FILE *fpout;
char line[100];
char *id;
char *token;
char *buf = malloc(BUFLEN);
fp = fopen("infile", "r");
setvbuf ( fp , buf , _IOLBF, BUFLEN );
while (fgets(line, 100, fp) != NULL) {
id = strtok(line, "\t");
token = strtok(NULL, "\t");
char *fnout = malloc(strlen(id)+5);
fnout = strcat(fnout, id);
fnout = strcat(fnout, ".seq");
fpout = fopen(fnout, "w");
setvbuf ( fpout , NULL , _IONBF , 0 );
fprintf(fpout, "%s", token);
fclose(fpout);
}
fclose(fp);
return 0;
}
C ++
Si comporta bene ed è molto più facile da scrivere rispetto al vero C. Hai ogni sorta di cose che ti tengono per mano (specialmente quando si tratta di stringhe e input). Tutto ciò significa che puoi effettivamente semplificare la logica. strtok
in C è un porco perché elabora l'intera stringa e quindi dobbiamo fare tutta quella noiosa allocazione di memoria. Questo scorre lungo la linea fino a quando non tocca la linguetta e tiriamo fuori i segmenti di cui abbiamo bisogno.
#include <fstream>
#include <string>
using namespace std;
int main(void) {
ifstream in("infile");
ofstream out;
string line;
while(getline(in, line)) {
string::size_type tab = line.find('\t', 0);
string filename = line.substr(0, tab) + ".seq";
out.open(filename.c_str());
out << line.substr(tab + 1);
out.close();
}
in.close();
}
GNU Parallel
(Non la versione moreutils). È una bella sintassi concisa ma OMGSLOW. Potrei usarlo male.
parallel --colsep '\t' echo {2} \> {1}.seq <infile
Test del generatore di cavi
Ecco il mio generatore di dati per 100000 linee di [ATGC] * 64. Non è veloce e i miglioramenti sono i benvenuti.
cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile
awk
è ancora una buona risposta per qualcosa di meno di decine di milioni. Anche se ridimensionate [linearmente] fino a un miliardo di righe, C vi fa risparmiare solo 1,5 ore su Perl e 3,6 ore su awk.