Desempenho de sed
vs. tail
para remover a primeira linha de um arquivo
TL; DR
-
sed
é muito poderoso e versátil, mas é isso que o torna lento, especialmente para arquivos grandes com muitas linhas. -
tail
faz apenas uma coisa simples, mas ela é boa e rápida, mesmo para arquivos maiores com muitas linhas.
Para arquivos pequenos e médios, sed
e tail
têm desempenho semelhante rápido (ou lento, dependendo das suas expectativas). No entanto, para arquivos de entrada maiores (vários MBs), a diferença de desempenho aumenta significativamente (uma ordem de magnitude para arquivos no intervalo de centenas de MBs), com tail
claramente superando sed
.
Experiência
Preparações gerais:
Nossos comandos para analisar são:
sed '1d' testfile > /dev/null
tail -n +2 testfile > /dev/null
Observe que estou direcionando a saída para /dev/null
a cada vez para eliminar a saída do terminal ou as gravações de arquivo como gargalo de desempenho.
Vamos configurar um disco RAM para eliminar a E / S de disco como um possível gargalo. Eu, pessoalmente, tenho um tmpfs
montado em /tmp
, então simplesmente coloquei meu testfile
para esse experimento.
Então eu já criei um arquivo de teste aleatório contendo uma quantidade especificada de linhas $numoflines
com comprimento de linha aleatório e dados aleatórios usando este comando (note que definitivamente não é ideal, torna-se muito lento para linhas de > 2M, mas quem se importa, não é a coisa que estamos analisando):
cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n "$numoflines" > testfile
Ah, btw. meu laptop de teste está executando o Ubuntu 16.04, 64 bits em uma CPU Intel i5-6200U. Apenas para comparação.
Grandes arquivos com tempo:
Configuração de um enorme testfile
:
A execução do comando acima com numoflines=10000000
produziu um arquivo aleatório contendo 10 milhões de linhas, ocupando um pouco mais de 600 MB - é muito grande, mas vamos começar com isso, porque podemos:
$ wc -l testfile
10000000 testfile
$ du -h testfile
611M testfile
$ head -n 3 testfile
qOWrzWppWJxx0e59o2uuvkrfjQbzos8Z0RWcCQPMGFPueRKqoy1mpgjHcSgtsRXLrZ8S4CU8w6O6pxkKa3JbJD7QNyiHb4o95TSKkdTBYs8uUOCRKPu6BbvG
NklpTCRzUgZK
O/lcQwmJXl1CGr5vQAbpM7TRNkx6XusYrO
Execute a execução cronometrada com nosso enorme testfile
:
Agora vamos fazer apenas uma única execução cronometrada com os dois comandos, primeiro para estimar com que grandezas estamos trabalhando.
$ time sed '1d' testfile > /dev/null
real 0m2.104s
user 0m1.944s
sys 0m0.156s
$ time tail -n +2 testfile > /dev/null
real 0m0.181s
user 0m0.044s
sys 0m0.132s
Já vemos um resultado muito claro para arquivos grandes, tail
é uma magnitude mais rápida que sed
. Mas apenas por diversão e para ter certeza de que não há efeitos colaterais aleatórios fazendo uma grande diferença, vamos fazer isso 100 vezes:
$ time for i in {1..100}; do sed '1d' testfile > /dev/null; done
real 3m36.756s
user 3m19.756s
sys 0m15.792s
$ time for i in {1..100}; do tail -n +2 testfile > /dev/null; done
real 0m14.573s
user 0m1.876s
sys 0m12.420s
A conclusão permanece a mesma, sed
é ineficiente para remover a primeira linha de um arquivo grande, tail
deve ser usado lá.
E sim, eu sei que as construções de loop de Bash são lentas, mas estamos fazendo apenas algumas iterações aqui e o tempo que um loop simples leva não é significativo comparado com sed
/ tail
runtimes.
Pequenos arquivos de tempo:
Configurando um pequeno testfile
:
Agora, para completar, vamos ver o caso mais comum de você ter um pequeno arquivo de entrada no intervalo kB. Vamos criar um arquivo de entrada aleatório com numoflines=100
, assim:
$ wc -l testfile
100 testfile
$ du -h testfile
8,0K testfile
$ head -n 3 testfile
tYMWxhi7GqV0DjWd
pemd0y3NgfBK4G4ho/
aItY/8crld2tZvsU5ly
Execute a execução cronometrada com nosso pequeno testfile
:
Como podemos esperar que os tempos para que esses arquivos pequenos fiquem no intervalo de alguns milissegundos de experiência, vamos fazer 1000 iterações imediatamente:
$ time for i in {1..1000}; do sed '1d' testfile > /dev/null; done
real 0m7.811s
user 0m0.412s
sys 0m7.020s
$ time for i in {1..1000}; do tail -n +2 testfile > /dev/null; done
real 0m7.485s
user 0m0.292s
sys 0m6.020s
Como você pode ver, os tempos são bem parecidos, não há muito o que interpretar ou imaginar. Para arquivos pequenos, as duas ferramentas são igualmente adequadas.