Minha resposta instantânea teria sido, awk
mas se você estiver processando muitas linhas - e eu estou falando de milhões - provavelmente verá um benefício real ao mudar para uma linguagem de programação "real".
Com isso em mente (e awk
já sendo tomado como resposta), escrevi algumas implementações em diferentes idiomas e as comparei no mesmo conjunto de dados de 10.000 linhas em um 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 relance, o C parece melhor, mas era um porco conseguir correr tão rápido. Pypy e C ++ são muito mais fáceis de escrever e executar com bom desempenho, a menos que você esteja falando de muitos bilhões de linhas. Se esse fosse o caso, uma atualização para fazer tudo isso na RAM ou em um SSD poderia ser um investimento melhor do que uma melhoria no código.
Obviamente, durante o tempo que passei por isso, você provavelmente processou algumas centenas de milhões de registros na opção mais lenta . Se você pode escrever apenas awk
loops ou Bash, faça isso e continue com a vida. Eu claramente tinha muito tempo livre hoje.
Também testei algumas opções multithread (em C ++ e Python e híbridos com GNU parallel
), mas a sobrecarga de threads supera completamente qualquer benefício para uma operação tão simples (divisão de caracteres, gravação).
Perl
awk
( gawk
aqui) seria honestamente o meu primeiro porto de escala para testar dados como esse, mas você pode fazer coisas bastante semelhantes no Perl. Sintaxe semelhante, mas com um identificador de escrita um pouco melhor.
perl -ane 'open(my $fh, ">", $F[0].".seq"); print $fh $F[1]; close $fh;' infile
Pitão
Eu gosto de Python. É a linguagem do meu trabalho diário e é apenas uma linguagem agradável, sólida e incrivelmente legível. Mesmo um iniciante provavelmente poderia adivinhar o que está acontecendo aqui.
with open("infile", "r") as f:
for line in f:
id, chunk = line.split()
with open(id + ".seq", "w") as fw:
fw.write(chunk)
Você deve se lembrar que o python
binário da sua distribuição não é a única implementação do Python disponível. Quando eu executei esse mesmo teste no Pypy, era mais rápido que o C, sem qualquer otimização lógica adicional. Lembre-se disso antes de escrever o Python como uma "linguagem lenta".
C
Comecei este exemplo para ver o que realmente poderíamos fazer minha CPU, mas, francamente, C é um pesadelo para codificar se você não o toca há muito tempo. Isso tem a desvantagem de estar limitado a linhas de 100 caracteres, embora seja muito simples expandir isso, eu simplesmente não precisava disso.
Minha versão original era mais lenta que o C ++ e o pypy, mas depois de escrever sobre o blog, recebi ajuda de Julian Klode . Esta versão é agora a mais rápida devido aos seus buffers de E / S ajustados. Também é muito mais longo e mais envolvido do que qualquer outra coisa.
#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 ++
Tem bom desempenho e é muito mais fácil escrever do que o real C. Você tem todo tipo de coisa que segura sua mão (especialmente quando se trata de strings e entradas). Tudo isso significa que você pode realmente simplificar a lógica. strtok
em C é um porco, porque processa toda a cadeia e, em seguida, precisamos fazer toda essa alocação cansativa de memória. Isso apenas passa pela linha até atingir a aba e puxamos os segmentos conforme necessário.
#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
(Não é a versão moreutils). É uma sintaxe concisa agradável, mas OMGSLOW. Eu posso estar usando errado.
parallel --colsep '\t' echo {2} \> {1}.seq <infile
Gerador de equipamento de teste
Aqui está meu gerador de dados para 100000 linhas de [ATGC] * 64. Não é rápido e as melhorias são muito bem-vindas.
cat /dev/urandom | tr -dc 'ATGC' | fold -w 64 | awk 'NR>100000{exit}{printf NR"\t"$0"\n"}' > infile
awk
ainda é uma boa resposta para algo menos que dezenas de milhões. Mesmo se você escalar linearmente isso em até um bilhão de linhas, C estará economizando apenas 1,5 horas em Perl e 3,6 horas em awk.