Qual é mais rápido para excluir a primeira linha no arquivo ... sed ou cauda?

13

Nesta resposta ( Como posso remover a primeira linha de um arquivo com sed? ) há duas maneiras de excluir o primeiro registro em um arquivo:

sed '1d' $file >> headerless.txt

** ---------------- OU ---------------- **

tail -n +2 $file >> headerless.txt

Pessoalmente, eu acho que a opção tail é cosmeticamente mais agradável e mais legível, mas provavelmente porque eu sou sed-desafiada.

Qual método é mais rápido?

    
por WinEunuuchs2Unix 20.12.2016 / 21:32

5 respostas

24

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.

    
por Byte Commander 20.12.2016 / 23:25
5

Aqui está outra alternativa, usando apenas bash builtins e cat :

{ read ; cat > headerless.txt; } < $file

$file é redirecionado para o agrupamento de comandos { } . O read simplesmente lê e descarta a primeira linha. O restante do fluxo é canalizado para cat , que o grava no arquivo de destino.

No meu Ubuntu 16.04, o desempenho deste e da solução tail são muito semelhantes. Eu criei um arquivo de teste com seq :

$ seq 100000000 > 100M.txt
$ ls -l 100M.txt 
-rw-rw-r-- 1 ubuntu ubuntu 888888898 Dec 20 17:04 100M.txt
$

tail solution:

$ time tail -n +2 100M.txt > headerless.txt

real    0m1.469s
user    0m0.052s
sys 0m0.784s
$ 

cat / brace solution:

$ time { read ; cat > headerless.txt; } < 100M.txt 

real    0m1.877s
user    0m0.000s
sys 0m0.736s
$ 

Eu só tenho um Ubuntu VM acessível agora, e vi variações significativas nos tempos de ambos, embora estejam todos na mesma situação.

    
por Digital Trauma 21.12.2016 / 01:14
4

Experimentando meu sistema e prefixando cada comando com time , obtive os seguintes resultados:

sed:

real    0m0.129s
user    0m0.012s
sys     0m0.000s

e cauda:

real    0m0.003s
user    0m0.000s
sys     0m0.000s

que sugerem que, no meu sistema, pelo menos, AMD FX 8250 rodando o Ubuntu 16.04, a cauda é significativamente mais rápida. O arquivo de teste tinha 10.000 linhas com um tamanho de 540k. O arquivo foi lido em um HDD.

    
por Nick Sillito 20.12.2016 / 22:02
1

Não há uma maneira objetiva de dizer qual é o melhor, porque sed e tail não são as únicas coisas executadas em um sistema durante a execução do programa. Muitos fatores, como i / o de disco, i / o de rede, interrupções de CPU para processos de prioridade mais alta - todos influenciam a rapidez com que seu programa será executado.

Ambos são escritos em C, então isso não é questão de linguagem, mas mais de questão ambiental. Por exemplo, eu tenho SSD e no meu sistema isso levará tempo em microssegundos, mas para o mesmo arquivo no disco rígido levará mais tempo porque os HDDs são significativamente mais lentos. Então, o hardware também desempenha papel nisso.

Há algumas coisas que você pode querer ter em mente ao considerar qual comando escolher:

  • Qual é o seu objetivo? sed é editor de fluxo para transformar texto. tail é para a saída de linhas específicas de texto. Se você quiser lidar com linhas e apenas imprimi-las, use tail . Se você quiser editar o texto, use sed .
  • tail tem uma sintaxe muito mais simples que sed , então use o que você pode ler a si mesmo e ao que outras pessoas podem ler.

Outro fator importante é a quantidade de dados que você está processando. Arquivos pequenos não oferecem nenhuma diferença de desempenho. A imagem fica interessante quando você está lidando com arquivos grandes. Com um BIGFILE.txt de 2 GB, podemos ver que sed tem muito mais chamadas do sistema do que tail e é executado consideravelmente mais lentamente.

bash-4.3$ du -sh BIGFILE.txt 
2.0G    BIGFILE.txt
bash-4.3$ strace -c  sed '1d' ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 59.38    0.079781           0    517051           read
 40.62    0.054570           0    517042           write
  0.00    0.000000           0        10         1 open
  0.00    0.000000           0        11           close
  0.00    0.000000           0        10           fstat
  0.00    0.000000           0        19           mmap
  0.00    0.000000           0        12           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         2           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         7         7 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         2         2 statfs
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.134351               1034177        11 total
bash-4.3$ strace -c  tail  -n +2 ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 62.30    0.148821           0    517042           write
 37.70    0.090044           0    258525           read
  0.00    0.000000           0         9         3 open
  0.00    0.000000           0         8           close
  0.00    0.000000           0         7           fstat
  0.00    0.000000           0        10           mmap
  0.00    0.000000           0         4           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         3         3 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.238865                775615         7 total
    
por Sergiy Kolodyazhnyy 20.12.2016 / 22:03
0

Outras respostas mostram bem o que é melhor criar um novo arquivo com a primeira linha faltando. Se você quiser editar um arquivo ao invés de criar um novo arquivo, eu aposto que ed seria mais rápido porque não deveria criar um novo arquivo. Mas você tem que procurar como remover uma linha com ed porque eu usei apenas uma vez.

    
por akostadinov 21.12.2016 / 20:08