Eu tenho uma situação única em que posso comparar as soluções propostas nesta página e, portanto, estou escrevendo esta resposta como uma consolidação das soluções propostas, com tempos de execução incluídos para cada um.
Configuração
Eu tenho um arquivo de dados de texto ASCII de 3.261 gigabytes com um par de valores-chave por linha. O arquivo contém 3,339,550,320 linhas no total e desafia a abertura em qualquer editor que eu tenha tentado, incluindo o meu go-to Vim. Preciso agrupar esse arquivo para investigar alguns dos valores que descobri que começam apenas na linha ~ 500.000.000.
Porque o arquivo tem tantas linhas:
- Eu preciso extrair apenas um subconjunto das linhas para fazer algo útil com os dados.
- Lendo todas as linhas que levam aos valores de que me importo vai demorar muito tempo.
- Se a solução ler além das linhas de que me preocupo e continuar lendo o restante do arquivo, perderá tempo lendo quase 3 bilhões de linhas irrelevantes e levará 6x mais tempo do que o necessário.
Meu melhor cenário é uma solução que extrai apenas uma única linha do arquivo sem ler nenhuma das outras linhas do arquivo, mas não consigo pensar em como fazer isso no Bash.
Para os fins de minha sanidade mental, não vou tentar ler as 500.000.000 linhas completas necessárias para o meu próprio problema. Em vez disso, tentarei extrair a linha 50.000.000 de 3.339.550.320 (o que significa que a leitura do arquivo completo demorará 60 vezes mais que o necessário).
Eu usarei o time
built-in para comparar cada comando.
Linha de base
Primeiro vamos ver como a head
tail
solução:
$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0
real 1m15.321s
A linha de base da linha 50 milhões é 00: 01: 15.321, se eu tivesse ido direto para a linha 500 milhões, provavelmente seria ~ 12,5 minutos.
cortar
Eu duvido disso, mas vale a pena tentar:
$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0
real 5m12.156s
Este levou 00: 05: 12.156 para ser executado, o que é muito mais lento que a linha de base! Não tenho certeza se ele leu o arquivo inteiro ou apenas 50 milhões antes da parada, mas independentemente isso não parece uma solução viável para o problema.
AWK
Eu só executei a solução com o exit
porque não iria esperar a execução do arquivo completo:
$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0
real 1m16.583s
Esse código foi executado em 00: 01: 16.583, que é apenas ~ 1 segundo mais lento, mas ainda não é uma melhoria na linha de base. Nesse ritmo, se o comando exit tivesse sido excluído, provavelmente levaria cerca de ~ 76 minutos para ler o arquivo inteiro!
Perl
Também executei a solução Perl existente:
$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0
real 1m13.146s
Esse código foi executado em 00: 01: 13.146, que é ~ 2 segundos mais rápido que a linha de base. Se eu executasse o total de 500.000.000, provavelmente levaria ~ 12 minutos.
sed
A melhor resposta no quadro, aqui está o meu resultado:
$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0
real 1m12.705s
Esse código foi executado em 00: 01: 12.705, que é 3 segundos mais rápido que a linha de base e ~ 0,4 segundos mais rápido que Perl. Se eu o executasse em 500.000.000 de linhas, provavelmente levaria cerca de 12 minutos.
mapfile
Tenho bash 3.1 e, portanto, não é possível testar a solução mapfile.
Conclusão
Parece que, na maioria das vezes, é difícil melhorar a head
tail
solução. Na melhor das hipóteses, a sed
solução fornece um aumento de ~ 3% na eficiência.
(porcentagens calculadas com a fórmula % = (runtime/baseline - 1) * 100
)
Linha 50.000.000
- 00: 01: 12.705 (-00: 00: 02.616 = -3,47%)
sed
- 00: 01: 13.146 (-00: 00: 02.175 = -2,89%)
perl
- 00: 01: 15,321 (+00: 00: 00.000 = + 0,00%)
head|tail
- 00: 01: 16,583 (+00: 00: 01,262 = + 1,68%)
awk
- 00: 05: 12,156 (+00: 03: 56,835 = + 314,43%)
cut
Linha 500.000.000
- 00: 12: 07,050 (-00: 00: 26.160)
sed
- 00: 12: 11,460 (-00: 00: 21.750)
perl
- 00: 12: 33,210 (+00: 00: 00.000)
head|tail
- 00: 12: 45,830 (+00: 00: 12.620)
awk
- 00: 52: 01,560 (+00: 40: 31,650)
cut
Linha 3.338.559.320
- 01: 20: 54.599 (-00: 03: 05.327)
sed
- 01: 21: 24.045 (-00: 02: 25.227)
perl
- 01: 23: 49,273 (+00: 00: 00.000)
head|tail
- 01: 25: 13,548 (+00: 02: 35.735)
awk
- 05: 47: 23.026 (+04: 24: 26.246)
cut
awk
esed
e eu tenho certeza que alguém pode vir até com um one-liner Perl ou menos assim;)