Uso de recursos usando pipe e string aqui

16

Podemos obter o mesmo resultado usando os dois a seguir bash,

echo 'foo' | cat

e

cat <<< 'foo'

Minha pergunta é: qual a diferença entre esses dois no que diz respeito aos recursos utilizados e qual é o melhor?

Meu pensamento é que, ao usar o pipe, estamos usando um processo extra echoe o pipe, enquanto na string aqui apenas um descritor de arquivo está sendo usado cat.

utlamn
fonte

Respostas:

17

O canal é um arquivo aberto em um sistema de arquivos no kernel e não pode ser acessado como um arquivo regular em disco. Ele é armazenado em buffer automaticamente apenas em um determinado tamanho e, eventualmente, será bloqueado quando estiver cheio. Diferentemente dos arquivos originados em dispositivos de bloco, os pipes se comportam como dispositivos de caracteres e, portanto, geralmente não oferecem suporte, lseek()e os dados lidos a partir deles não podem ser lidos novamente, como você faria com um arquivo comum.

A string here é um arquivo comum criado em um sistema de arquivos montado. O shell cria o arquivo e mantém seu descritor, removendo imediatamente seu único link do sistema de arquivos (e excluindo-o) antes de gravar / ler um byte no / do arquivo. O kernel manterá o espaço necessário para o arquivo até que todos os processos liberem todos os descritores para ele. Se a criança que estiver lendo esse descritor tiver a capacidade de fazê-lo, poderá ser retrocedida lseek()e lida novamente.

Nos dois casos, os tokens <<<e |representam descritores de arquivos e não necessariamente os próprios arquivos. Você pode ter uma idéia melhor do que está acontecendo, fazendo coisas como:

readlink /dev/fd/1 | cat

...ou...

ls -l <<<'' /dev/fd/*

A diferença mais significativa entre os dois arquivos é que o here-string / doc é praticamente um caso de uma vez - o shell grava todos os dados nele antes de oferecer o descritor de leitura para a criança. Por outro lado, o shell abre o canal nos descritores apropriados e extrai os filhos para gerenciar os do canal - e, portanto, é escrito / lido simultaneamente nas duas extremidades.

Essas distinções, no entanto, são geralmente verdadeiras. Até onde eu sei (isso não é tão longe assim), isso é verdade para praticamente todos os shell que manipulam a <<<mão curta da string <<here para um redirecionamento aqui do documento com a única exceção de yash. yash, busybox, dash, E outras ashvariantes tendem a voltar aqui-documentos com tubos, porém, e assim naqueles conchas realmente há muito pouca diferença entre os dois depois de tudo.

Ok - duas exceções. Agora que estou pensando sobre isso, ksh93na verdade não funciona |, mas lida com todo o negócio com soquetes - embora faça um arquivo tmp excluído para a <<<*maioria dos outros. Além disso, ele apenas coloca as seções separadas de um pipeline em um ambiente de subconjuntos, que é uma espécie de eufemismo POSIX, pelo menos que age como um subconjuntos , e nem mesmo os garfos.

O fato é que os resultados de referência do @ PSkocik (o que é muito útil) aqui podem variar bastante por vários motivos, e a maioria deles depende da implementação. Para a configuração do documento aqui, os maiores fatores serão o ${TMPDIR}tipo de sistema de arquivos de destino e a configuração / disponibilidade atual do cache e ainda mais a quantidade de dados a serem gravados. Para o tubo, ele terá o tamanho do próprio processo de casca, porque são feitas cópias para os garfos necessários. Dessa maneira, bashé terrível na configuração do pipeline (para incluir substituições de $(comando )) - porque é grande e muito lento, mas com ksh93isso praticamente não faz diferença.

Aqui está outro pequeno trecho de shell para demonstrar como um shell divide subshells para um pipeline:

pipe_who(){ echo "$$"; sh -c 'echo "$PPID"'; }
pipe_who
pipe_who | { pipe_who | cat /dev/fd/3 -; } 3<&0

32059  #bash's pid
32059  #sh's ppid
32059  #1st subshell's $$
32111  #1st subshell sh's ppid
32059  #2cd subshell's $$
32114  #2cd subshell sh's ppid

A diferença entre o que uma pipe_who()chamada em pipeline relata e o relatório de uma execução no shell atual se deve ao comportamento especificado por um (subshell )de reivindicar o pid do shell pai $$quando ele é expandido. Embora bashsubshells definitivamente sejam processos separados, o $$parâmetro shell especial não é uma fonte confiável dessas informações. Ainda assim, o shshell filho do subshell não recusa relatar com precisão o seu $PPID.

mikeserv
fonte
Muito útil. O sistema de arquivos no kernel, existe um nome para ele? significa que existe no espaço do kernel?
utlamn
2
@utlamn - na verdade, sim - simplesmente pipefs . É tudo no kernel - mas (além de coisas como FUSE) assim é todo arquivo i / o .
mikeserv
10

Não há substituto para o benchmarking:

pskocik@ProBook:~ 
$ time (for((i=0;i<1000;i++)); do cat<<< foo >/dev/null; done  )

real    0m2.080s
user    0m0.738s
sys 0m1.439s
pskocik@ProBook:~ 
$ time (for((i=0;i<1000;i++)); do echo foo |cat >/dev/null; done  )

real    0m4.432s
user    0m2.095s
sys 0m3.927s
$ time (for((i=0;i<1000;i++)); do cat <(echo foo) >/dev/null; done  )
real    0m3.380s
user    0m1.121s
sys 0m3.423s

E para uma quantidade maior de dados:

TENMEG=$(ruby -e 'puts "A"*(10*1024*1024)')
pskocik@ProBook:~ 
$ time (for((i=0;i<100;i++)); do echo "$TENMEG" |cat >/dev/null; done  )

real    0m42.327s
user    0m38.591s
sys 0m4.226s
pskocik@ProBook:~ 
$ time (for((i=0;i<100;i++)); do cat<<< "$TENMEG" >/dev/null; done  )

real    1m26.946s
user    1m23.116s
sys 0m3.681s
pskocik@ProBook:~ 

$ time (for((i=0;i<100;i++)); do cat <(echo "$TENMEG") >/dev/null; done  )

real    0m43.910s
user    0m40.178s
sys 0m4.119s

Parece que a versão do pipe tem um custo de instalação maior, mas no final é mais eficiente.

PSkocik
fonte
@mikeserv Isso estava correto. Eu adicionei uma referência com uma quantidade maior de dados.
PSKocik 02/08/2015
2
echo foo >/dev/shm/1;cat /dev/shm/1 >/dev/nullparecia ser rápido em ambos os casos ...
user23013
@ user23013 Isso faz sentido. Não vejo por que um echo "$longstring"ou outro <<<"$longstring"seria otimizado em termos de eficiência e, com seqüências curtas, a eficiência não importa muito.
PSKocik 03/08/19
É interessante que no meu caso (no Ubuntu 14.04, Intel quad core i7) cat <(echo foo) >/dev/nullseja mais rápido que echo foo | cat >/dev/null.
precisa saber é
11
@ Prem Sim, essa seria uma abordagem melhor, mas uma ainda melhor seria não se preocupar com isso e usar a ferramenta certa para o trabalho. Não há razão para pensar que os heredocs seriam ajustados ao desempenho.
PSKocik