Estou comparando o seguinte
tail -n 1000000 stdout.log | grep -c '"success": true'
tail -n 1000000 stdout.log | grep -c '"success": false'
com o seguinte
log=$(tail -n 1000000 stdout.log)
echo "$log" | grep -c '"success": true'
echo "$log" | grep -c '"success": false'
e surpreendentemente o segundo demora quase três vezes mais que o primeiro. Deveria ser mais rápido, não deveria?
bash
performance
io
phunehehe
fonte
fonte
$( command substitution )
é transmitido. Todo o resto acontece através de pipes simultaneamente, mas no segundo exemplo, você precisa aguardar a conclusão do processo. Experimente com << HERE \ n $ {log = $ (command)} \ nHERE - veja o que você recebe.log=
grep
, você poderá ver alguma aceleração usando,tee
para que o arquivo seja definitivamente lido apenas uma vez.cat stdout.log | tee >/dev/null >(grep -c 'true'>true.cnt) >(grep -c 'false'>false.cnt); cat true.cnt; cat false.cnt
tail -n 10000 | fgrep -c '"success": true'
e false.Respostas:
Por um lado, o primeiro método chama
tail
duas vezes, portanto, ele tem que fazer mais trabalho do que o segundo método, que só faz isso uma vez. Por outro lado, o segundo método precisa copiar os dados para o shell e, em seguida, voltar atrás, portanto, ele tem que fazer mais trabalho do que a primeira versão na qualtail
é diretamente canalizadogrep
. O primeiro método possui uma vantagem extra em uma máquina com vários processadores:grep
pode trabalhar em paralelo comtail
, enquanto o segundo método é estritamente serializado, primeirotail
e depoisgrep
.Portanto, não há razão óbvia para que um seja mais rápido que o outro.
Se você quiser ver o que está acontecendo, veja como o sistema chama o shell. Tente com conchas diferentes também.
Com o método 1, os principais estágios são:
tail
lê e procura encontrar o seu ponto de partida.tail
escreve pedaços de 4096 bytes quegrep
lê tão rápido quanto são produzidos.Com o método 2, os principais estágios são:
tail
lê e procura encontrar o seu ponto de partida.tail
grava pedaços de 4096 bytes que o bash lê 128 bytes por vez e o zsh lê 4096 bytes por vez.grep
lidos tão rapidamente quanto são produzidos.Os blocos de 128 bytes do Bash ao ler a saída da substituição de comando diminuem significativamente; O zsh é tão rápido quanto o método 1 para mim. Sua milhagem pode variar dependendo do tipo e número da CPU, configuração do planejador, versões das ferramentas envolvidas e tamanho dos dados.
fonte
st_blksize
valor de um canal, que é 4096 nesta máquina (e não sei se é porque é o tamanho da página da MMU). O 128 de Bash teria que ser uma constante embutida.Eu fiz o teste a seguir e no meu sistema a diferença resultante é cerca de 100 vezes mais longa para o segundo script.
Meu arquivo é uma saída strace chamada
bigfile
Scripts
Na verdade, não tenho correspondências para o grep, então nada é gravado no último canal até
wc -l
Aqui estão os horários:
Então, eu executei os dois scripts novamente através do comando strace
Aqui estão os resultados dos rastreamentos:
E p2.strace
Análise
Não é de surpreender que, em ambos os casos, a maior parte do tempo seja gasta aguardando a conclusão de um processo, mas p2 espera 2,63 vezes mais que p1 e, como outros já mencionaram, você está começando tarde no p2.sh.
Então agora esqueça o
waitpid
, ignore a%
coluna e observe a coluna dos segundos nos dois rastreamentos.O maior tempo em que p1 passa a maior parte do tempo na leitura provavelmente é compreensível, porque há um arquivo grande para ler, mas o p2 gasta 28,82 vezes mais em leitura do que o p1. -
bash
não espera ler um arquivo tão grande em uma variável e provavelmente está lendo um buffer de cada vez, dividindo-se em linhas e, em seguida, obtendo outro.a contagem de leituras p2 é 705k vs 84k para a p1, cada leitura exigindo uma alternância de contexto para o espaço do kernel e saindo novamente. Quase 10 vezes o número de leituras e alternâncias de contexto.
O tempo na gravação p2 passa 41,93 vezes mais na gravação do que na p1
o número de gravações p1 faz mais gravações que p2, 42k vs 21k, porém são muito mais rápidos.
Provavelmente por causa das
echo
linhas emgrep
oposição aos buffers de escrita de cauda.Além disso , p2 gasta mais tempo na gravação do que na leitura, p1 é o contrário!
Outro fator Veja o número de
brk
chamadas do sistema: o p2 gasta 2,42 vezes mais tempo do que a leitura! Na p1 (ele nem se registra).brk
é quando o programa precisa expandir seu espaço de endereço porque o suficiente não foi alocado inicialmente, isso provavelmente se deve ao bash ter que ler esse arquivo na variável e não esperar que ele seja tão grande e, como o @scai mencionou, se o o arquivo fica muito grande, mesmo isso não funcionaria.tail
é provavelmente um leitor de arquivos bastante eficiente, porque é para isso que ele foi projetado para fazer, provavelmente cria um mapa do arquivo e procura por quebras de linha, permitindo que o kernel otimize a E / S. bash não é tão bom, tanto no tempo gasto lendo e escrevendo.O p2 gasta 44ms e 41ms
clone
eexecv
não é um valor mensurável para o p1. Provavelmente bash lendo e criando a variável a partir da cauda.Finalmente, o total p1 executa ~ 150k chamadas do sistema vs p2 740k (4,93 vezes maior).
Eliminando waitpid, p1 gasta 0,014416 segundos na execução de chamadas do sistema, p2 0,439132 segundos (30 vezes mais).
Portanto, parece que o p2 passa a maior parte do tempo no espaço do usuário sem fazer nada, exceto aguardando a conclusão das chamadas do sistema e o kernel para reorganizar a memória, o p1 realiza mais gravações, mas é mais eficiente e causa uma carga do sistema significativamente menor e, portanto, é mais rápido.
Conclusão
Eu nunca tentaria me preocupar com a codificação através da memória ao escrever um script bash, isso não significa dizer que você não tenta ser eficiente.
tail
foi projetado para fazer o que faz, provavelmentememory maps
o arquivo para que seja eficiente para ler e permita que o kernel otimize a E / S.Uma maneira melhor de otimizar seu problema pode ser o primeiro
grep
para '' sucesso '': as linhas e depois contar as verdades e falsidades,grep
tem uma opção de contagem que novamente evitawc -l
, ou melhor ainda, canaliza a caudaawk
e conta verdades e falsifica simultaneamente. O p2 não apenas demora, mas adiciona carga ao sistema enquanto a memória está sendo embaralhada com brks.fonte
Na verdade, a primeira solução também lê o arquivo na memória! Isso é chamado de armazenamento em cache e é feito automaticamente pelo sistema operacional.
E, como já explicado corretamente pelo mikeserv, a primeira solução é executada
grep
enquanto o arquivo está sendo lido, enquanto a segunda solução a executa após a leitura do arquivotail
.Portanto, a primeira solução é mais rápida devido a várias otimizações. Mas isso nem sempre precisa ser verdade. Para arquivos realmente grandes que o sistema operacional decide não armazenar em cache, a segunda solução pode se tornar mais rápida. Mas observe que, para arquivos ainda maiores, que não cabem na memória, a segunda solução não funciona.
fonte
Eu acho que a principal diferença é muito simples
echo
e lenta. Considere isto:Como você pode ver acima, a etapa demorada é imprimir os dados. Se você simplesmente redirecionar para um novo arquivo e grep, é muito mais rápido ao ler o arquivo apenas uma vez.
E conforme solicitado, com uma string here:
Essa é ainda mais lenta, presumivelmente porque a string here está concatenando todos os dados em uma linha longa e isso reduzirá a velocidade
grep
:Se a variável for citada para que não ocorra divisão, as coisas serão um pouco mais rápidas:
Mas ainda é lento porque a etapa de limitação da taxa está imprimindo os dados.
fonte
<<<
, seria interessante ver se isso faz diferença?Eu tentei isso também ... Primeiro, criei o arquivo:
Se você executar o procedimento acima, deverá criar 1,5 milhões de linhas
/tmp/log
com uma proporção de 2: 1 de"success": "true"
linhas para"success": "false"
linhas.A próxima coisa que fiz foi executar alguns testes. Eu executei todos os testes por meio de um proxy,
sh
portanto,time
seria necessário apenas assistir a um único processo - e, portanto, poderia mostrar um único resultado para todo o trabalho.Este parece ser o mais rápido, apesar de adicionar um segundo descritor de arquivo e
tee,
embora eu possa explicar por que:Aqui está o seu primeiro:
E o seu segundo:
Você pode ver que, em meus testes, houve mais de uma diferença de velocidade de 3 * ao lê-la em uma variável, como você fez.
Eu acho que parte disso é que uma variável do shell precisa ser dividida e manipulada pelo shell quando está sendo lida - não é um arquivo.
A,
here-document
por outro lado, para todos os efeitos, é umfile
- defile descriptor,
qualquer maneira. E como todos sabemos - o Unix trabalha com arquivos.O que é mais interessante para mim
here-docs
é que você pode manipulá-losfile-descriptors
- como um straight|pipe
- e executá-los. Isso é muito útil, pois permite um pouco mais de liberdade em apontar para|pipe
onde você deseja.Eu tinha
tee
dotail
porque os primeirosgrep
come ashere-doc |pipe
e está lá nada para a segunda leitura. Mas desde que eu|piped
lo em/dev/fd/3
e pegou de novo para passar para>&1 stdout,
ele não importava muito. Se você usargrep -c
tantas outras, recomende:É ainda mais rápido.
Mas quando eu o executo sem
. sourcing
oheredoc
êxito, não consigo obter o segundo plano com êxito do primeiro processo para executá-los de forma totalmente simultânea. Aqui está, sem fundo completo:Mas quando eu adiciono o
&:
Ainda assim, a diferença parece ser de apenas alguns centésimos de segundo, pelo menos para mim, então aceite como quiser.
De qualquer forma, o motivo pelo qual ele é executado mais rapidamente
tee
é porque ambos sãogreps
executados ao mesmo tempo com apenas uma chamada detail. tee
duplicatas do arquivo para nós e o dividem para o segundogrep
processo, todos in-stream - tudo é executado ao mesmo tempo do começo ao fim. todos terminam na mesma hora também.Então, voltando ao seu primeiro exemplo:
E o seu segundo:
Mas quando dividimos nossa entrada e executamos nossos processos simultaneamente:
fonte