Por que é 'arquivo tac | grep foo '(piping) mais rápido que' grep foo (tac file) '(substituição de processo)?

7

Esta pergunta foi motivada por " Reverter o grepping ", sobre o grepping de um arquivo enorme de de baixo para cima.

@chaos disse :

tac file | grep whatever

Or a bit more effective:

grep whatever < <(tac file)

@ vinc17 disse :

The < <(tac filename) should be as fast as a pipe

Também há muitos comentários interessantes de outros usuários.

Minhas perguntas:

  • Qual é a diferença entre | e < <() ?
  • Por que um é mais rápido que o outro?
  • E o que é realmente mais rápido?
  • Por que ninguém sugeriu xargs ?
por c0rp 24.07.2014 / 07:09

2 respostas

10

A construção <(tac file) faz com que o shell seja:

  • Crie um canal com um nome
    • Em sistemas como o Linux e o SysV que possuem /dev/fd , um canal regular é usado e /dev/fd/<the-file-descriptor-of-the-pipe> é usado como o nome.
    • Em outros sistemas, um canal nomeado é usado, o que requer a criação de uma entrada de arquivo real no disco.
  • Inicie o comando tac file e conecte-o a uma extremidade do canal.
  • Substitua toda a construção na linha de comando pelo nome do pipe.

Após a substituição, a linha de comando se torna:

grep whatever < /tmp/whatever-name-the-shell-used-for-the-named-pipe

E então grep é executado, e lê sua entrada padrão (que é o canal), lê e procura seu primeiro argumento.

Assim, o resultado final é o mesmo que com ...

tac file | grep whatever

... em que os mesmos dois programas são lançados e um pipe ainda é usado para conectá-los. Mas a construção <( ... ) é mais complicada porque envolve mais etapas e pode envolver um arquivo temporário (o pipe nomeado).

A construção <( ... ) é uma extensão e não está disponível no shell padrão POSIX bourne nem em plataformas que não suportam /dev/fd ou pipes nomeados. Só por esse motivo, porque as duas alternativas consideradas são exatamente equivalentes em termos de funcionalidade, a forma mais flexível de command | other-command é a melhor escolha.

A construção <( ... ) deve ser mais lenta devido à convolução adicional, mas é apenas na fase de inicialização e não espero que a diferença seja facilmente mensurável.

NOTA : Em plataformas Linux SysV, < ( ... ) não usa pipes nomeados, mas usa pipes normais. Canais regulares (na verdade, todos os descritores de arquivo) podem ser referenciados pelo especial chamado /dev/fd/<file-descriptor-number , de modo que o shell usa como um nome para o pipe. Desta forma, evita criar um pipe nomeado real com um nome de arquivo temporário genuíno no sistema de arquivos real. Embora o truque /dev/fd seja o usado para implementar esse recurso quando aparece originalmente em ksh , é uma otimização: em plataformas que não suportam isso, um canal nomeado regular no sistema de arquivos real é usado conforme descrito acima .

TAMBÉM NOTE : Para descrever a sintaxe, pois <<( ... ) é enganoso. Na verdade, é <( ... ) , que é substituído pelo nome de um canal, e então o outro caractere < que prefixa a coisa toda é separado dessa sintaxe e é a sintaxe regular bem conhecida para redirecionar a entrada de um arquivo.

    
por 24.07.2014 / 08:06
7

What is difference between | and <<()?

Existe uma diferença entre eles:

  • | causa cada comando executado em um subshell separado.

  • <() executa o comando, que é substituído em segundo plano.

Para as próximas duas perguntas, faremos um pouco de strace :

pipe :

$ strace -fc bash -c 'tac /usr/share/dict/american-english | grep qwerty'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.008120        2707         3         1 wait4
  0.00    0.000000           0       352           read
  0.00    0.000000           0       229           write
  0.00    0.000000           0        20         2 open
  0.00    0.000000           0        29         2 close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0       117           lseek
  0.00    0.000000           0        38           mmap
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        25           brk
  0.00    0.000000           0        22           rt_sigaction
  0.00    0.000000           0        18           rt_sigprocmask
  0.00    0.000000           0         1           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0        24        12 access
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         2           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         2           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00    0.008120                  1034        37 total

Process Substitution :

$ strace -fc bash -c 'grep qwerty < <(tac /usr/share/dict/american-english)'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.14    0.016001        4000         4         2 wait4
  0.46    0.000075           0       229           write
  0.24    0.000038           0       341           read
  0.16    0.000026           1        24           brk
  0.00    0.000000           0        21         2 open
  0.00    0.000000           0        27           close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0       117           lseek
  0.00    0.000000           0        38           mmap
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        35           rt_sigaction
  0.00    0.000000           0        24           rt_sigprocmask
  0.00    0.000000           0         2           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0        24        12 access
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         3           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         3           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1         1 fcntl
  0.00    0.000000           0         2           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00    0.016140                  1046        37 total

Why something faster than other?

And what is really faster?

Você pode ver que process substitution é mais lento que pipe nesse caso, porque usa mais chamadas do sistema. Ambos gastam muito tempo para aguardar processos filhos, mas process substitution usa mais wait4() syscall e usa mais tempo para cada chamada do que pipe .

Why no one suggest xargs ?

Eu não acho que xargs possa ajudar em nada aqui, isso não é o seu trabalho.

Atualizar

Como sugerido pelo @Gilles, faço um teste com um arquivo maior, dados aleatórios de 2 GB gerados a partir de /dev/urandom . Isso mostra que pipe é realmente mais rápido que process substitution .

pipe :

$ strace -fc bash -c 'tac sample.txt | grep qwerty'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 81.15    8.284959     2761653         3         1 wait4
 17.89    1.825959           2    780959           read
  0.91    0.092708           0    524286           write
  0.05    0.005364           0    262146           lseek
  0.00    0.000000           0        20         2 open
  0.00    0.000000           0        29         2 close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0        38           mmap
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        25           brk
  0.00    0.000000           0        22           rt_sigaction
  0.00    0.000000           0        18           rt_sigprocmask
  0.00    0.000000           0         1           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0        24        12 access
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         2           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         2           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00   10.208990               1567727        37 total

process substitution :

$ strace -fc bash -c 'grep qwerty < <(tac sample.txt)'
$ time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.51   13.912869     3478217         4         2 wait4
  0.38    0.053373           0    655269           read
  0.09    0.013084           0    524286           write
  0.02    0.002454           0    262146           lseek
  0.00    0.000030           1        38           mmap
  0.00    0.000024           1        24        12 access
  0.00    0.000000           0        21         2 open
  0.00    0.000000           0        27           close
  0.00    0.000000           0        40        17 stat
  0.00    0.000000           0        19           fstat
  0.00    0.000000           0        18           mprotect
  0.00    0.000000           0         6           munmap
  0.00    0.000000           0        24           brk
  0.00    0.000000           0        35           rt_sigaction
  0.00    0.000000           0        24           rt_sigprocmask
  0.00    0.000000           0         2           rt_sigreturn
  0.00    0.000000           0         3         2 ioctl
  0.00    0.000000           0         1           pipe
  0.00    0.000000           0         3           dup2
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         3           clone
  0.00    0.000000           0         3           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1         1 fcntl
  0.00    0.000000           0         2           getrlimit
  0.00    0.000000           0        13           getuid
  0.00    0.000000           0        13           getgid
  0.00    0.000000           0        13           geteuid
  0.00    0.000000           0        13           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         3           arch_prctl
  0.00    0.000000           0         1           time
------ ----------- ----------- --------- --------- ----------------
100.00   13.981834               1442060        37 total
    
por 24.07.2014 / 08:31