¿Cómo puedo optimizar este comando de Unix?

¿Cómo puedo optimizar este comando de Unix?

El siguiente comando tarda unos 10 minutos en generar el resultado

find . -name "muc*_*_20160920_*.unl*" | xargs zcat |
    awk -F "|" '{if($14=="20160920100643" && $22=="567094398953") print $0}'| head

¿Cómo puedo mejorar su rendimiento?

Respuesta1

Eso ya está bastante optimizado. Es difícil saber cuál es el cuello de botella sin conocer más detalles como:

  • tipo de almacenamiento (HD, SSD, red, RAID)
  • número y tamaño promedio de archivos coincidentes
  • número de directorios y otros archivos que no coinciden
  • número de campos en cada línea
  • longitud promedio de una línea

Cosas que puedes hacer en cualquier caso:

  • reemplácelo -print | xargscon -exec cmd {} +o -print0 | xargs -r0si lo findadmite xargs. -print | xargsno sólo es incorrecto sino que también es más costoso ya que xargsnecesita decodificar caracteres para descubrir cuáles están en blanco y realizar un costoso procesamiento de cotizaciones.
  • fije la configuración regional en C ( export LC_ALL=C). Dado que todos los caracteres involucrados aquí ( |y dígitos decimales para el contenido del archivo y letras latinas, punto y guión bajo para los nombres de archivo) son parte del juego de caracteres portátil, si su juego de caracteres es UTF-8 o algún otro juego de caracteres multibyte, cambie a C con su conjunto de caracteres de un solo byte ahorrará mucho trabajo para findy awk.
  • Simplifique la awkparte para: awk -F "|" '$14 == "20160920100643" && $22 == "567094398953"'.
  • Dado que está canalizando la salida a head, es posible que desee desactivar el almacenamiento en búfer de salida para awkque genere esas 10 líneas lo antes posible. Con gawko mawk, puedes usarlo fflush()para eso. O podrías agregar un if (++n == 10) exitin awk.

Para resumir:

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -exec zcat {} + |
  awk -F "|" '$14 == "20160920100643" && $22 == "567094398953" {
    print; if (++n == 10) exit}')

Si la CPU es el cuello de botella, en un sistema GNU multinúcleo, podrías intentar:

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -print0 |
  xargs -r0P 4 -n 100 sh -c '
    zcat "$@" | 
      awk -F "|" "\$14 == "20160920100643" && \$22 == "567094398953" {
        print; fflush()}"' sh | head)

Para ejecutar 4 zcat | awktrabajos en paralelo en lotes de 100 archivos.

Si se 20160920100643trata de una marca de tiempo, es posible que desee excluir los archivos que se modificaron por última vez antes de esa fecha. Con GNU o BSD find, agregue un archivo -newermt '2016-09-20 10:06:42'.

Si las líneas tienen una gran cantidad de campos, recibirás una penalización por awkdividirlas y asignar tantos $ncampos. Usar un enfoque que solo considere los primeros 22 campos podría acelerar las cosas:

grep -E '^([^|]*\|){13}20160920100643(\|[^|]*){7}\|567094398953(\||$)'

en lugar del awkcomando. Con GNU grep, agregue la --line-bufferedopción de generar las líneas lo antes posible en el enfoque paralelo o -m 10detenerlas después de 10 coincidencias en el enfoque no paralelo.

Para resumir, si la CPU es el cuello de botella y tienes al menos 4 núcleos de CPU en tu sistema y hay al menos 400 archivos muc* y estás en un sistema GNU (donde grepsuele ser significativamente más rápido que GNU awk):

(export LC_ALL=C
find . -name "muc*_*_20160920_*.unl*" -newermt '2016-09-20 10:06:42' -print0 |
  xargs -r0P 4 -n 100 sh -c '
    zcat "$@" | 
      grep --line-buffered -E \
        "^([^|]*\|){13}20160920100643(\|[^|]*){7}\|567094398953(\||$)"
  ' sh | head)

Tenga en cuenta que en el enfoque paralelo, es posible que la salida de los grepcomandos se mezcle (aunque con el almacenamiento en búfer de línea y las líneas proporcionadas tienen menos de unos pocos kilobytes de tamaño, los límites de las líneas deben conservarse).

Respuesta2

La respuesta de @ Stéphane Chazelas proporciona muchos detalles sobre cómo optimizar la canalización de comandos.

find . -name "muc*_*_20160920_*.unl*" | xargs zcat |
    awk -F "|" '{if($14=="20160920100643" && $22=="567094398953") print $0}'| head

Voy a proporcionar otra forma de abordar el problema en la que realmente mides dónde pasas la mayor parte del tiempo. Una vez que sepa dónde se dedica el tiempo, podrá determinar qué hacer al respecto. Si quieres mejorar tu tiempo de carrera de 10 minutos, optimizar un paso que dura 2 segundos es casi inútil.

Cuando miro la canalización de comandos, me llaman la atención tres cosas:

  1. find .- ¿Cómo es la estructura del directorio? ¿Cuántos archivos por directorio? ¿El directorio es local para el sistema en el que se ejecuta el comando? Un sistema de archivos remoto será unloteMás lento.
  2. -name "muc*_*_20160920_*.unl*"- ¿Qué tan cerca están todos los nombres de archivos en la estructura de directorios? ¿Están todos "cercanos" al nombre y son difíciles de igualar o requieren un uso intensivo de CPU? PorquecadaEl nombre del archivo en el árbol de directorios debe leerse del disco y compararse con el patrón.
  3. xargs zcat- xargsNo me parece que sea un gran problema de rendimiento, especialmente en comparación con los findproblemas anteriores y con el zcatmismo. Incluso si se trata de 10.000 o incluso 10.000.000 de nombres de archivos, el tiempo empleado en pasar y analizar sólo los nombres es casi con certeza insignificante en comparación con el tiempo invertidohallazgolos nombres y luego abrir y descomprimir todos los archivos. ¿Qué tamaño tienen los archivos? Porque estás descomprimiendo la totalidad decadaarchivo que coincida con findel patrón de nombre de archivo.

¿Cómo se puede determinar cuál es el principal problema de rendimiento? Mida el rendimiento de cada comando en la canalización. (Verhttps://stackoverflow.com/questions/13294554/how-to-use-gnu-time-with-pipelinepara obtener detalles sobre cómo cronometrar una canalización completa). Puede ejecutar los siguientes comandos y ver cuánto tiempo contribuye cada paso al tiempo de procesamiento de toda la canalización:

/usr/bin/time find .- Esto le indica cuánto tiempo lleva ejecutar el árbol de directorios. Si esto es lento, necesitas un mejor sistema de almacenamiento. Vacíe la caché de su sistema de archivosantes de cronometrar esto para obtener una medición del peor de los casos, luego ejecute el cronometrado findnuevamente y vea cuánto afecta el almacenamiento en caché al rendimiento. Y si el directorio no es local, intente ejecutar el comando en el sistema real en el que se encuentran los archivos.

/usr/bin/time find . -name "muc*_*_20160920_*.unl*"- Esto le indicará cuánto tiempo lleva hacer coincidir el patrón de los nombres de los archivos. Nuevamente, vacíe la caché del sistema de archivos y ejecútelo dos veces.

/usr/bin/time bash -c "find . -name 'muc*_*_20160920_*.unl*' | xargs zcat > /dev/null"- Este es el que sospecho que es el componente principal del largo tiempo de ejecución de su canalización. Si este es el problema, paralelizar los zcatcomandos según la respuesta de Stéphane Chazela puede ser la mejor respuesta.

Continúe agregando pasos desde la canalización de comandos original a la que se está probando hasta que encuentre dónde pasa la mayor parte de su tiempo. Nuevamente sospecho que es el zcatpaso. Si es así, quizás la zcatparalelización que publicó @Stéphane Chazelas ayude.

Es posible que el paralelismo zcatno ayude; incluso puede queherirrendimiento y procesamiento lento. Con solo uno zcatejecutándose a la vez, IO puede tener un patrón de transmisión agradable que minimice las búsquedas de disco. Con múltiples zcatprocesos ejecutándose a la vez, las operaciones de IO pueden competir y, de hecho, ralentizar el procesamiento ya que los cabezales de disco necesitan buscar y cualquier lectura anticipada realizada se vuelve menos efectiva.

Si el zcatpaso es su principal cuello de botella en el rendimiento y ejecutar múltiples zcatprocesos al mismo tiempo no lo ayuda o en realidad lo ralentiza, su canalización está limitada por IO y debe abordar el problema mediante el uso de un almacenamiento más rápido.

Y nuevamente, si el directorio no es local en la máquina en la que ejecuta la canalización de comandos, intente ejecutarlo en la máquina en la que realmente se encuentra el sistema de archivos.

Respuesta3

Como se señaló en los comentarios.zgrepes una mejor opción para este tipo de tareas conestrella globularopción que permite utilizar **comoall path inside the directory except hidden

shopt -s globstar
zgrep -m 10 '^\([^|]*|\)\{13\}20160920100643|\([^|]*|\)\{7\}567094398953' ./**muc*_*_20160920_*.unl*
shopt -u globstar

Respuesta4

Como se señaló, no es posible dar la respuesta correcta sin algunos detalles adicionales.

locate -0 -b -r '^muc.*_.*_20160920_.*.unl.*gz' | 
   xargs -0  zcat |
   awk -F "|" '$14=="20160920100643" && $22=="567094398953"'| head
  • 1: localizar (si está disponible) es mucho más rápido que **o find; La expresión regular utilizada debe ajustarse...

  • 2 y 3: el filtro del PO

Como @rudimeier señaló sabiamente, existen problemas relacionados con la disponibilidad y el estado de actualización de locate. (por ejemplo, en la mayoría de las máquinas Linux, la ubicación se actualiza diariamente; de ​​esta manera no podrá encontrar los archivos creados hoy)

Sin embargo, si la localización está disponible, esto producirá una aceleración impresionante.

Sería interesante que la OP pudiera proporcionar time ...varias soluciones.

información relacionada