Hice la siguiente prueba y en mi sistema la diferencia resultante es aproximadamente 100 veces más larga para el segundo script.
Mi archivo es una salida extraña llamada bigfile
$ wc -l bigfile.log
1617000 bigfile.log
Guiones
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
En realidad no tengo coincidencias para el grep, por lo que no se escribe nada en la última tubería hasta wc -l
Aquí están los horarios:
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
Así que ejecuté los dos scripts nuevamente a través del comando strace
strace -cfo p1.strace bash p1.sh
strace -cfo p2.strace bash p2.sh
Aquí están los resultados de las huellas:
$ 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
Y 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álisis
No es sorprendente que, en ambos casos, la mayor parte del tiempo se pase esperando a que se complete un proceso, pero p2 espera 2,63 veces más que p1, y como otros han mencionado, está comenzando tarde en p2.sh.
Así que ahora olvídate de waitpid
, ignora la %
columna y mira la columna de segundos en ambas trazas.
El tiempo más largo que p1 pasa la mayor parte de su tiempo en lectura probablemente sea comprensible, porque hay un archivo grande para leer, pero p2 pasa 28.82 veces más tiempo en lectura que p1. - bash
no espera leer un archivo tan grande en una variable y probablemente esté leyendo el búfer a la vez, dividiéndolo en líneas y luego obteniendo otro.
El recuento de lectura p2 es 705k vs 84k para p1, cada lectura requiere un cambio de contexto en el espacio del kernel y de nuevo. Casi 10 veces el número de lecturas y cambios de contexto.
El tiempo en escritura p2 pasa 41.93 veces más en escritura que p1
el recuento de escritura p1 realiza más escrituras que p2, 42k frente a 21k, sin embargo, son mucho más rápidos.
Probablemente debido a las echo
líneas en los grep
buffers de escritura de cola.
Además , p2 pasa más tiempo en escritura que en lectura, ¡p1 es al revés!
Otro factor Observe la cantidad de brk
llamadas al sistema: ¡p2 gasta 2.42 veces más tiempo de interrupción de lo que lee! En p1 (ni siquiera se registra). brk
es cuando el programa necesita expandir su espacio de direcciones porque inicialmente no se asignó suficiente, esto probablemente se deba a que bash tiene que leer ese archivo en la variable, y no esperar que sea tan grande, y como mencionó @scai, si el el archivo se hace demasiado grande, incluso eso no funcionaría.
tail
es probablemente un lector de archivos bastante eficiente, porque para esto fue diseñado, probablemente mapee el archivo y escanee en busca de saltos de línea, permitiendo así que el núcleo optimice la E / S. bash no es tan bueno tanto en el tiempo dedicado a leer como a escribir.
p2 gasta 44ms y 41ms clone
y execv
no es una cantidad medible para p1. Probablemente bash leyendo y creando la variable desde la cola.
Finalmente, Totals p1 ejecuta ~ 150k llamadas al sistema frente a p2 740k (4.93 veces mayor).
Eliminando waitpid, p1 gasta 0.014416 segundos ejecutando llamadas al sistema, p2 0.439132 segundos (30 veces más).
Por lo tanto, parece que p2 pasa la mayor parte del tiempo en el espacio del usuario sin hacer nada excepto esperar a que se completen las llamadas del sistema y que el kernel reorganice la memoria, p1 realiza más escrituras, pero es más eficiente y causa una carga de sistema significativamente menor, y por lo tanto es más rápido.
Conclusión
Nunca trataría de preocuparme por la codificación a través de la memoria al escribir un script bash, eso no significa que no intentes ser eficiente.
tail
está diseñado para hacer lo que hace, probablemente sea memory maps
el archivo para que sea eficiente de leer y permita que el núcleo optimice la E / S.
Una mejor manera de optimizar su problema podría ser primero grep
para '' éxito '': líneas y luego contar las verdades y falsos, grep
tiene una opción de conteo que nuevamente evita wc -l
, o incluso mejor, canalizar la cola awk
y contar las verdades y falsos al mismo tiempo. p2 no solo lleva mucho tiempo, sino que agrega carga al sistema mientras la memoria se baraja con brks.