Estoy comparando lo siguiente
tail -n 1000000 stdout.log | grep -c '"success": true'
tail -n 1000000 stdout.log | grep -c '"success": false'
con lo siguiente
log=$(tail -n 1000000 stdout.log)
echo "$log" | grep -c '"success": true'
echo "$log" | grep -c '"success": false'
y sorprendentemente, el segundo tarda casi 3 veces más que el primero. Debería ser más rápido, ¿no?
bash
performance
io
phunehehe
fuente
fuente
$( command substitution )
es decir , no se transmiten. Todo lo demás sucede a través de tuberías al mismo tiempo, pero en el segundo ejemplo, debe esperar alog=
que se complete. Pruébelo con << AQUÍ \ n $ {log = $ (comando)} \ nAQUÍ: vea lo que obtiene.grep
para, es posible que vea un poco de aceleracióntee
para que el archivo definitivamente solo se lea una 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'
falso.Respuestas:
Por un lado, el primer método llama
tail
dos veces, por lo que tiene que hacer más trabajo que el segundo método que solo hace esto una vez. Por otro lado, el segundo método tiene que copiar los datos en el shell y luego retroceder, por lo que tiene que hacer más trabajo que la primera versión dondetail
se canaliza directamentegrep
. El primer método tiene una ventaja adicional en una máquina multiprocesador:grep
puede funcionar en paralelo contail
, mientras que el segundo método es estrictamente serializado, primerotail
y luegogrep
.Por lo tanto, no hay una razón obvia por la cual una sea más rápida que la otra.
Si quieres ver qué está pasando, mira qué llamadas al sistema hace el shell. Prueba con diferentes conchas también.
Con el método 1, las etapas principales son:
tail
lee y busca encontrar su punto de partida.tail
escribe fragmentos de 4096 bytes que segrep
leen tan rápido como se producen.Con el método 2, las etapas principales son:
tail
lee y busca encontrar su punto de partida.tail
escribe fragmentos de 4096 bytes que bash lee 128 bytes a la vez y zsh lee 4096 bytes a la vez.grep
leen tan rápido como se producen.Los fragmentos de 128 bytes de Bash al leer el resultado de la sustitución del comando lo ralentiza significativamente; zsh sale casi tan rápido como el método 1 para mí. Su kilometraje puede variar según el tipo y número de CPU, la configuración del planificador, las versiones de las herramientas involucradas y el tamaño de los datos.
fuente
st_blksize
valor de una tubería, que es 4096 en esta máquina (y no sé si es porque es el tamaño de página de MMU). Los 128 de Bash tendrían que ser una constante incorporada.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
Guiones
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:
Así que ejecuté los dos scripts nuevamente a través del comando strace
Aquí están los resultados de las huellas:
Y p2.strace
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 losgrep
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
yexecv
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 seamemory 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 evitawc -l
, o incluso mejor, canalizar la colaawk
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.fuente
En realidad, la primera solución también lee el archivo en la memoria. Esto se llama almacenamiento en caché y el sistema operativo lo hace automáticamente.
Y como ya explica correctamente mikeserv, la primera solución se ejecuta
grep
mientras se lee el archivo, mientras que la segunda solución lo ejecuta después de que el archivo haya sido leídotail
.Entonces, la primera solución es más rápida debido a varias optimizaciones. Pero esto no siempre tiene que ser cierto. Para archivos realmente grandes que el sistema operativo decide no almacenar en caché, la segunda solución podría ser más rápida. Pero tenga en cuenta que para archivos aún más grandes que no caben en su memoria, la segunda solución no funcionará en absoluto.
fuente
Creo que la principal diferencia es muy simple, que
echo
es lenta. Considera esto:Como puede ver arriba, el paso que lleva mucho tiempo es imprimir los datos. Si simplemente redirige a un nuevo archivo y lo revisa, es mucho más rápido cuando solo lee el archivo una vez.
Y según lo solicitado, con una cadena aquí:
Este es aún más lento, presumiblemente porque la cadena here está concatenando todos los datos en una línea larga y eso ralentizará
grep
:Si la variable se cita para que no ocurra división, las cosas son un poco más rápidas:
Pero sigue siendo lento porque el paso de limitación de velocidad es imprimir los datos.
fuente
<<<
Sería interesante ver si eso marca la diferencia.También probé esto ... Primero, construí el archivo:
Si ejecuta lo anterior usted mismo, debería obtener 1,5 millones de líneas
/tmp/log
con una proporción de 2: 1 de"success": "true"
líneas a"success": "false"
líneas.Lo siguiente que hice fue ejecutar algunas pruebas. Ejecuté todas las pruebas a través de un proxy,
sh
portime
lo que solo tendría que ver un solo proceso y, por lo tanto, podría mostrar un solo resultado para todo el trabajo.Este parece ser el más rápido, aunque agrega un segundo descriptor de archivos y
tee,
creo que puedo explicar por qué:Aquí está tu primero:
Y tu segundo:
Puede ver que en mis pruebas hubo una diferencia de velocidad de más de 3 * al leerla en una variable como lo hizo.
Creo que parte de eso es que una variable de shell debe ser dividida y manejada por el shell cuando se lee, no es un archivo.
A,
here-document
por otro lado, para todos los efectos, es afile
- a defile descriptor,
todos modos. Y como todos sabemos, Unix funciona con archivos.Lo que es más interesante para mí
here-docs
es que puedes manipularlosfile-descriptors
, como una escalera|pipe
, y ejecutarlos. Esto es muy útil ya que te permite un poco más de libertad para apuntar hacia|pipe
donde quieras.Tenía que
tee
eltail
debido a que los primerosgrep
come lashere-doc |pipe
y de allí no queda nada para el segundo para leer. Pero ya que|piped
lo metí/dev/fd/3
y lo recogí nuevamente para pasarlo>&1 stdout,
, no importó demasiado. Si usagrep -c
como muchos otros, recomiende:Es incluso más rápido
Pero cuando lo ejecuto sin
. sourcing
elheredoc
no puedo ejecutar con éxito el primer proceso para ejecutarlos completamente al mismo tiempo. Aquí está sin fondo completo:Pero cuando agrego el
&:
Aún así, la diferencia parece ser solo unas pocas centésimas de segundo, al menos para mí, así que tómalo como quieras.
De todos modos, la razón por la que se ejecuta más rápido
tee
es porque ambos segreps
ejecutan al mismo tiempo con solo una invocación detail. tee
duplicados del archivo para nosotros y lo divide en el segundogrep
proceso todo en flujo: todo se ejecuta a la vez de principio a fin, por lo que todos terminan al mismo tiempo también.Volviendo a tu primer ejemplo:
Y tu segundo:
Pero cuando dividimos nuestra entrada y ejecutamos nuestros procesos simultáneamente:
fuente