Mi respuesta rápida habría sido, awk
pero si está procesando muchas líneas, y estoy hablando de millones, probablemente verá un beneficio real al cambiar a un lenguaje de programación "real".
Con eso en mente (y awk
ya lo estoy tomando como respuesta), escribí algunas implementaciones en diferentes idiomas y las comparé en el mismo conjunto de datos de 10,000 líneas en 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
De un vistazo, la C se ve mejor, pero fue un cerdo correr tan rápido. Pypy y C ++ son mucho más fáciles de escribir y funcionan lo suficientemente bien a menos que esté hablando de miles de millones de líneas. Si ese fuera el caso, una actualización para hacer todo esto en RAM o en un SSD podría ser una mejor inversión que una mejora de código.
Obviamente, en el tiempo que pasé revisando estos, probablemente podría haber procesado unos cientos de millones de registros en la opción más lenta . Si solo puede escribir awk
o Bash loops, hágalo y continúe con la vida. Claramente tuve mucho tiempo libre hoy.
También probé algunas opciones de subprocesos múltiples (en C ++ y Python e híbridos con GNU parallel
) pero la sobrecarga de los subprocesos supera por completo cualquier beneficio para una operación tan simple (división de cadenas, escritura).
Perl
awk
( gawk
aquí) honestamente sería mi primer puerto de escala para probar datos como este, pero puede hacer cosas bastante similares en Perl. Sintaxis similar pero con un controlador de escritura ligeramente mejor
perl -ane 'open(my $fh, ">", $F[0].".seq"); print $fh $F[1]; close $fh;' infile
Pitón
Me gusta Python Es mi idioma de trabajo diario y es un lenguaje agradable, sólido e increíblemente legible. Incluso un principiante probablemente podría adivinar lo que está sucediendo aquí.
with open("infile", "r") as f:
for line in f:
id, chunk = line.split()
with open(id + ".seq", "w") as fw:
fw.write(chunk)
Debe recordar que el python
binario de su distribución no es la única implementación de Python que existe. Cuando ejecuté esta misma prueba a través de Pypy, fue más rápido que C sin ninguna otra optimización lógica. Tenga esto en cuenta antes de escribir Python como un "lenguaje lento".
C
Comencé este ejemplo para ver lo que realmente podríamos hacer que mi CPU haga, pero, francamente, C es una pesadilla para codificar si no lo has tocado en mucho tiempo. Esto tiene el inconveniente adicional de estar limitado a líneas de 100 caracteres, aunque es muy simple expandir eso, simplemente no lo necesitaba.
Mi versión original era más lenta que C ++ y pypy, pero después de bloguear sobre ella , recibí ayuda de Julian Klode . Esta versión es ahora la más rápida debido a sus buffers IO ajustados. También es mucho más largo y más complicado que cualquier otra 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 ++
Se desempeña bien y es mucho más fácil de escribir que el C. real. Tiene todo tipo de cosas que sostienen su mano (especialmente cuando se trata de cadenas y entrada). Todo eso significa que puedes simplificar la lógica. strtok
en C es un cerdo porque procesa toda la cadena y luego tenemos que hacer toda esa asignación de memoria agotadora. Esto solo se mueve a lo largo de la línea hasta que toca la pestaña y sacamos los segmentos cuando los necesitamos.
#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 Paralelo
(No es la versión moreutils). Es una buena sintaxis concisa pero OMGSLOW. Podría estar usándolo mal.
parallel --colsep '\t' echo {2} \> {1}.seq <infile
Generador de arnés de prueba
Aquí está mi generador de datos para 100000 líneas de [ATGC] * 64. No es rápido y las mejoras son bienvenidas.
cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile
awk
sigue siendo una buena respuesta para menos de decenas de millones. Incluso si escala [linealmente] esto hasta mil millones de líneas, C solo le ahorrará 1.5 horas sobre Perl y 3.6 horas sobre awk.paste <(yes A) <(yes T) <(yes G) <(yes C) | head -n1600000 | tr '\t' '\n' | shuf | tr -d \\n | fold -w64 | cat -n > infile
.Implementación de shell puro:
fuente
Utilizando
awk
:Desde el nominado
file
, imprima el segundo campo en cada registro ($2
) en un archivo con el nombre del primer campo ($1
) junto.seq
con el nombre.Como Thor señala en los comentarios, para un gran conjunto de datos, puede agotar los descriptores de archivo, por lo que sería aconsejable cerrar cada archivo después de escribir :
fuente
close($1".seq")
.awk
embargo, algunas implementaciones como GNU saben cómo solucionarlo.Aquí hay una manera de hacerlo con GNU sed:
O más eficientemente, como lo sugiere Glenn Jackman :
fuente
awk
es probablemente la herramienta más eficiente para usar. Por supuesto, tiene razón sobre no generarsh
cada línea, he agregado la opción de tubería como alternativa.