Moja odpowiedź byłaby szybka, awk
ale jeśli przetwarzasz wiele linii - a mówię o milionach - prawdopodobnie zobaczysz prawdziwą korzyść z przejścia na „prawdziwy” język programowania.
Mając to na uwadze (i awk
już jako odpowiedź) napisałem kilka implementacji w różnych językach i porównałem je w tym samym zestawie danych o pojemności 10 000 linii na dysku 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
Na pierwszy rzut oka C wygląda najlepiej, ale to była świnia, żeby biegać tak szybko. Pypy i C ++ są znacznie łatwiejsze do napisania i działają wystarczająco dobrze, chyba że mówimy o wielu miliardach linii. W takim przypadku uaktualnienie do robienia tego wszystkiego w pamięci RAM lub na dysku SSD może być lepszą inwestycją niż ulepszenie kodu.
Oczywiście w czasie, który spędziłem na ich przeglądaniu, prawdopodobnie mógłbyś przetworzyć kilkaset milionów rekordów w najwolniejszej opcji . Jeśli możesz pisać awk
lub zapętlać Bash, rób to i żyj dalej. Najwyraźniej dzisiaj miałem za dużo wolnego czasu.
Testowałem także kilka opcji wielowątkowych (w C ++ i Python oraz hybryd z GNU parallel
), ale narzuty wątków całkowicie przewyższają wszelkie korzyści z tak prostej operacji (dzielenie ciągów, pisanie).
Perl
awk
( gawk
tutaj) byłbym szczerze moim pierwszym portem do testowania takich danych, ale w Perlu możesz robić dość podobne rzeczy. Podobna składnia, ale z nieco lepszym uchwytem do pisania.
perl -ane 'open(my $fh, ">", $F[0].".seq"); print $fh $F[1]; close $fh;' infile
Pyton
I jak Python. To mój język pracy i po prostu ładny, solidny i niesamowicie czytelny język. Nawet początkujący prawdopodobnie mógłby zgadnąć, co się tutaj dzieje.
with open("infile", "r") as f:
for line in f:
id, chunk = line.split()
with open(id + ".seq", "w") as fw:
fw.write(chunk)
Musisz pamiętać, że python
plik binarny twojej dystrybucji nie jest jedyną implementacją Pythona. Kiedy przeprowadziłem ten sam test przez Pypy, było ono szybsze niż C bez dalszej optymalizacji logiki. Pamiętaj o tym, zanim wypisz Python jako „wolny język”.
do
Zacząłem ten przykład, aby zobaczyć, co naprawdę możemy zrobić z moim procesorem, ale szczerze mówiąc, C jest koszmarem do kodowania, jeśli nie dotknąłeś go od dawna. Ma to tę dodatkową wadę, że ogranicza się do linii o długości 100 znaków, chociaż bardzo łatwo ją rozwinąć, po prostu jej nie potrzebowałem.
Moja oryginalna wersja była wolniejsza niż C ++ i pypy, ale po blogowaniu na ten temat otrzymałem pomoc od Juliana Klode . Ta wersja jest teraz najszybsza ze względu na ulepszone bufory IO. Jest także znacznie dłuższy i bardziej zaangażowany niż cokolwiek innego.
#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 ++
Działa dobrze i jest znacznie łatwiejszy do pisania niż prawdziwy C. Masz wiele rzeczy, które trzymają cię za rękę (szczególnie jeśli chodzi o napisy i dane wejściowe). Wszystko to oznacza, że możesz uprościć logikę. strtok
w C jest wieprzem, ponieważ przetwarza cały ciąg, a następnie musimy wykonać całą tę męczącą alokację pamięci. Po prostu przesuwa się wzdłuż linii, aż dotknie zakładki, i wyciągamy segmenty, gdy są potrzebne.
#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
(Nie wersja Moreutils). To miła, zwięzła składnia, ale OMGSLOW. Mogę źle go używać.
parallel --colsep '\t' echo {2} \> {1}.seq <infile
Test generatora wiązki przewodów
Oto mój generator danych dla 100000 linii [ATGC] * 64. To nie jest szybkie i ulepszenia są bardzo mile widziane.
cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile
awk
wciąż jest dobrą odpowiedzią na cokolwiek mniejszego niż dziesiątki milionów. Nawet jeśli [liniowo] skalujesz to do miliarda linii, C oszczędza ci tylko 1,5 godziny w Perlu i 3,6 godziny w awk.paste <(yes A) <(yes T) <(yes G) <(yes C) | head -n1600000 | tr '\t' '\n' | shuf | tr -d \\n | fold -w64 | cat -n > infile
. : .Implementacja czystej powłoki:
źródło
Używanie
awk
:Z nominowanego
file
wydrukuj drugie pole w każdym rekordzie ($2
) do pliku nazwanego po pierwszym polu ($1
) z.seq
dopiskiem do nazwy.Jak wskazuje Thor w komentarzach, w przypadku dużego zestawu danych możesz wyczerpać deskryptory plików, więc dobrze byłoby zamknąć każdy plik po napisaniu :
źródło
close($1".seq")
.awk
Jednak niektóre implementacje, takie jak GNU, potrafią to obejść.Oto jeden ze sposobów, w jaki możesz to zrobić za pomocą GNU sed:
Lub wydajniej, jak sugeruje glenn jackman :
źródło
awk
jest to prawdopodobnie najbardziej wydajne narzędzie do użycia. Oczywiście masz rację, że nie spawnujesz sięsh
dla każdej linii, dodałem opcję potoku jako alternatywę.