Uso do recurso usando pipe e string aqui

16

Podemos obter o mesmo resultado usando os dois seguintes em 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 deles é melhor?

Meu pensamento é que, enquanto estiver usando pipe, estamos usando um processo extra echo e pipe enquanto estiver aqui string apenas um descritor de arquivo está sendo usado com cat .

    
por utlamn 03.08.2015 / 00:05

2 respostas

17

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

A string here é um arquivo regular criado em um sistema de arquivos montado. O shell cria o arquivo e retém seu descritor ao mesmo tempo em que remove imediatamente seu único link do sistema de arquivos (e, portanto, o exclui) antes de gravar / ler um byte para / 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 lê a partir de tal descritor tiver a capacidade de fazê-lo, ela poderá ser rebobinada com lseek() e lida novamente.

Em ambos os casos, os tokens <<< e | representam descritores de arquivos e não necessariamente os próprios arquivos. Você pode ter uma ideia 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 aqui-string / doc é praticamente um caso todo-a-uma-vez - o shell grava todos os dados nele antes de oferecer o descritor lido até o filho. Por outro lado, o shell abre o pipe nos descritores apropriados e bifurca os filhos para gerenciar aqueles para o pipe - e por isso ele é gravado / lido simultaneamente nos dois extremos.

Essas distinções, no entanto, são apenas geralmente verdadeiras. Tanto quanto eu estou ciente (o que não é realmente tão longe) isso é verdade para praticamente todos os shell que lida com o short-hand <<< here-string para << a here- redirecionamento de documentos com a única exceção de yash . yash , busybox , dash , e outras ash variants tendem a voltar aqui - documentos com pipes, entretanto, e assim, naqueles shells há realmente pouca diferença entre os dois afinal.

Ok - duas exceções. Agora que estou pensando nisso, ksh93 não faz um pipe por | , mas lida com todo o negócio w / sockets - embora ele faça um arquivo tmp excluído para <<<* como a maioria outros fazem. Além disso, ele só coloca as seções separadas de um pipeline em um ambiente subshell que é um tipo de eufemismo POSIX para pelo menos ele age como um subshell , e assim não faz nem faça os garfos.

O fato é que os resultados do benchmark PSkocik (que é muito útil) aqui podem variar amplamente por muitos motivos, e a maioria deles depende da implementação. Para a configuração do documento aqui, os maiores fatores serão o ${TMPDIR} do tipo de sistema de arquivos e a configuração / disponibilidade do cache atual, e ainda mais a quantidade de dados a serem gravados. Para o pipe, será o tamanho do próprio processo de shell, porque são feitas cópias dos garfos necessários. Desta forma, bash é terrível na configuração do pipeline (para incluir $( command ) substituições) - porque é grande e muito lento, mas com ksh93 dificilmente faz alguma diferença.

Aqui está outro pequeno snippet de shell para demonstrar como um shell separa os subshells de 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 chamada de pipe_who() encadeada reporta e o relatório de uma execução no shell atual é devido a um% p% de ( do comportamento especificado de reclamar o pid do shell pai em ) quando é expandido. Embora $$ subshells definitivamente sejam processos separados, o parâmetro de shell especial bash não é uma fonte confiável dessas informações. Ainda assim, o filho da subshell $$ shell não declina de relatar com precisão seu sh .

    
por 03.08.2015 / 00:17
9

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.

    
por 03.08.2015 / 00:17