Eu fiz o seguinte teste e no meu sistema a diferença resultante é cerca de 100 vezes maior para o segundo script.
Meu arquivo é uma saída strace chamada bigfile
$ wc -l bigfile.log
1617000 bigfile.log
Scripts
xtian@clafujiu:~/tmp$ cat p1.sh
tail -n 1000000 bigfile.log | grep '"success": true' | wc -l
tail -n 1000000 bigfile.log | grep '"success": false' | wc -l
xtian@clafujiu:~/tmp$ cat p2.sh
log=$(tail -n 1000000 bigfile.log)
echo "$log" | grep '"success": true' | wc -l
echo "$log" | grep '"success": true' | wc -l
Eu não tenho nenhuma correspondência para o grep, então nada é escrito no último canal até wc -l
Aqui estão os horários:
xtian@clafujiu:~/tmp$ time bash p1.sh
0
0
real 0m0.381s
user 0m0.248s
sys 0m0.280s
xtian@clafujiu:~/tmp$ time bash p2.sh
0
0
real 0m46.060s
user 0m43.903s
sys 0m2.176s
Então, eu corri os dois scripts novamente por meio do comando strace
strace -cfo p1.strace bash p1.sh
strace -cfo p2.strace bash p2.sh
Aqui estão os resultados dos traços:
$ cat p1.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
97.24 0.508109 63514 8 2 waitpid
1.61 0.008388 0 84569 read
1.08 0.005659 0 42448 write
0.06 0.000328 0 21233 _llseek
0.00 0.000024 0 204 146 stat64
0.00 0.000017 0 137 fstat64
0.00 0.000000 0 283 149 open
0.00 0.000000 0 180 8 close
...
0.00 0.000000 0 162 mmap2
0.00 0.000000 0 29 getuid32
0.00 0.000000 0 29 getgid32
0.00 0.000000 0 29 geteuid32
0.00 0.000000 0 29 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 7 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 0.522525 149618 332 total
E p2.strace
$ cat p2.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
75.27 1.336886 133689 10 3 waitpid
13.36 0.237266 11 21231 write
4.65 0.082527 1115 74 brk
2.48 0.044000 7333 6 execve
2.31 0.040998 5857 7 clone
1.91 0.033965 0 705681 read
0.02 0.000376 0 10619 _llseek
0.00 0.000000 0 248 132 open
...
0.00 0.000000 0 141 mmap2
0.00 0.000000 0 176 126 stat64
0.00 0.000000 0 118 fstat64
0.00 0.000000 0 25 getuid32
0.00 0.000000 0 25 getgid32
0.00 0.000000 0 25 geteuid32
0.00 0.000000 0 25 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 6 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 1.776018 738827 293 total
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á iniciando tardiamente em p2.sh.
Então, esqueça o waitpid
, ignore a coluna %
e observe a coluna de segundos nos dois traços.
Maior tempo p1 gasta a maior parte do tempo em leitura provavelmente porque há um arquivo grande para ler, mas p2 gasta 28,82 vezes mais tempo em leitura que p1. - bash
não espera ler um arquivo tão grande em uma variável e provavelmente está lendo o buffer de cada vez, dividindo em linhas e, em seguida, obtendo outro.
leitura de leitura p2 é 705k vs 84k para p1, cada leitura requer uma mudança de contexto para o espaço do kernel e para fora novamente. Quase 10 vezes o número de leituras e interruptores de contexto.
Tempo em gravação p2 gasta 41,93 vezes mais tempo em gravação que p1
write count p1 faz mais gravações que p2, 42k vs 21k, no entanto elas são muito mais rápidas.
Provavelmente devido ao echo
de linhas em grep
em oposição a buffers de gravação de cauda.
Ainda mais , p2 gasta mais tempo em escrita do que em leitura, p1 é o contrário!
Outro fator Veja o número de brk
chamadas do sistema: p2 gasta 2,42 vezes mais tempo do que a leitura! Em p1 (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 fato de ter que ler esse arquivo na variável, e não esperar que ele seja tão grande, e como @scai mencionou , se o arquivo ficar muito grande, mesmo isso não funcionaria.
O
tail
é provavelmente um leitor de arquivos bastante eficiente, porque é para isso que ele foi projetado, ele provavelmente faz o mapeamento do arquivo e procura por quebras de linha, permitindo que o kernel otimize o i / o. O bash não não é tão bom tanto no tempo gasto lendo e escrevendo.
p2 gasta 44ms e 41ms em clone
e execv
não é uma quantia mensurável para p1. Provavelmente bash lendo e criando a variável 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 executando chamadas do sistema, p2 0,439132 segundos (30 vezes mais).
Portanto, parece que p2 passa a maior parte do tempo no espaço do usuário sem fazer nada exceto esperar que as chamadas do sistema sejam completadas e o kernel reorganize a memória, p1 executa mais gravações, mas é mais eficiente e causa significativamente menos carga do sistema 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, provavelmente memory maps
do arquivo, para que seja eficiente para ler e permitir que o kernel otimize a E / S.
Uma maneira melhor de otimizar seu problema pode ser primeiro grep
para '"sucesso":' linhas e depois contar os trues e os falsos, grep
tem uma opção de contagem que evita novamente o wc -l
ou mesmo melhor ainda, canalizar a cauda até awk
e contar trues e falsifica concorrentemente. p2 não só demora muito, mas adiciona carga ao sistema enquanto a memória está sendo embaralhada com brics.