Rendimiento de bucle vs expansión

9

Necesita sugerencias de expertos en la siguiente comparación:

Segmento de código usando el bucle:

for file in `cat large_file_list`
do
    gzip -d $file
done

Segmento de código con expansión simple:

gzip -d `cat large_file_list`

¿Cuál será más rápido? Tiene que manipular grandes conjuntos de datos.

León
fuente
1
La respuesta correcta dependerá del tiempo que tarde en iniciarse gzipen su sistema, el número de archivos en la lista de archivos y el tamaño de esos archivos.
Kusalananda
La lista de archivos tendrá alrededor de 1000 - 10000 archivos. El tamaño varía de algunos kilobytes a 500 MB. No tengo idea de cuánto tiempo lleva iniciar gzip en mi sistema. alguna forma de verificar?
Leon
1
Ok, entonces también puede depender de la longitud de los nombres de archivo . Si los nombres de archivo son largos, algunos sistemas pueden generar un error de "lista de argumentos demasiado larga" si intenta hacerlo sin un bucle ya que la sustitución del comando daría como resultado una línea de comando demasiado larga para que el shell se ejecute. Si no desea depender de la cantidad de archivos en la lista, simplemente use un bucle. ¿Pasa mucho tiempo descomprimiendo estos archivos en comparación con el otro procesamiento que realizará en ellos?
Kusalananda
Leon echa un vistazo a los resultados de mi prueba: "mi gran arglista" es 20 veces más rápido que el "bucle" en mi entorno.
para un medio feliz entre el inicio del proceso y la longitud de la línea de comando, use algo como, xargs gzip -d < large_file_listpero tenga cuidado con los espacios en los nombres de archivo, tal vez contr \\n \\0 large_file_list | xargs -0 gzip -d
w00t

Respuestas:

19

Complicaciones

Lo siguiente solo funcionará algunas veces:

gzip -d `cat large_file_list`

Hay tres problemas (en bashy la mayoría de los otros depósitos tipo Bourne):

  1. Fallará si algún nombre de archivo tiene una pestaña de espacio o caracteres de nueva línea (suponiendo que $IFSno se haya modificado). Esto se debe a la división de palabras del shell .

  2. También es probable que falle si algún nombre de archivo tiene caracteres glob-active. Esto se debe a que el shell aplicará la expansión del nombre de ruta a la lista de archivos.

  3. También fallará si los nombres de archivo comienzan con -(si POSIXLY_CORRECT=1eso solo se aplica al primer archivo) o si algún nombre de archivo es -.

  4. También fallará si hay demasiados nombres de archivo en él para caber en una línea de comando.

El código siguiente está sujeto a los mismos problemas que el código anterior (excepto el cuarto)

for file in `cat large_file_list`
do
    gzip -d $file
done

Solución confiable

Si large_file_listtiene exactamente un nombre de archivo por línea, y un archivo llamado -no está entre ellos, y está en un sistema GNU, entonces use:

xargs -rd'\n' gzip -d -- <large_file_list

-d'\n'le dice xargsque trate cada línea de entrada como un nombre de archivo separado.

-rle dice que xargsno ejecute el comando si el archivo de entrada está vacío.

--dice gzipque los siguientes argumentos no deben tratarse como opciones, incluso si comienzan con -. -sin embargo, solo se trataría como en -lugar del archivo llamado -.

xargspondrá muchos nombres de archivo en cada línea de comando pero no tantos que excedan el límite de la línea de comando. Esto reduce el número de veces que gzipse debe iniciar un proceso y, por lo tanto, lo hace más rápido. También es seguro: los nombres de los archivos también estarán protegidos contra la división de palabras y la expansión del nombre de ruta .

John1024
fuente
Gracias por la respuesta detallada Entiendo tus 3 problemas mencionados. El nombre del archivo es simple y no enfrentará esos desafíos, ya que la lista tendrá una capacidad de hasta 20000. Y mi pregunta es básicamente sobre el rendimiento de esos dos segmentos. Gracias.
Leon
1
@Leon El forbucle será, con diferencia, el más lento. Los otros dos métodos tendrán una velocidad muy cercana entre sí.
John1024
77
Además, no descarte los posibles problemas: muchas preguntas aquí en StackExchange se deben a que la división de palabras o la expansión del nombre de ruta le sucedió a personas que no lo esperaban.
John1024
55
Tenga en cuenta también que hay una variación en la lectura de un archivo con xargs: al menos la versión GNU tiene la --arg-fileopción (forma corta -a). Entonces uno podría hacer en su xargs -a large_file_list -rd'\n' gzip -d lugar. Efectivamente, no hay diferencia, aparte del hecho de que <es un operador de shell y xargsleería desde stdin (que shell "enlaza" al archivo), mientras -aque xargsabriría explícitamente el archivo en cuestión
Sergiy Kolodyazhnyy
2
Terdon señaló en otro comentario sobre el uso parallelpara ejecutar múltiples copias de gzip, pero xargs(al menos el GNU), también tiene el -Pinterruptor para eso. En máquinas multinúcleo que pueden marcar la diferencia. Pero también es posible que la descompresión esté completamente unida a E / S de todos modos.
ilkkachu
12

Dudo que importe mucho.

Usaría un bucle, solo porque no sé cuántos archivos están listados en el archivo de lista, y no sé (generalmente) si alguno de los nombres de archivo tiene espacios en sus nombres. Hacer una sustitución de comando que generaría una lista muy larga de argumentos puede dar como resultado un error "Lista de argumentos demasiado larga" cuando la longitud de la lista generada es demasiado larga.

Mi bucle se vería así

while IFS= read -r name; do
    gunzip "$name"
done <file.list

Esto también me permitiría insertar comandos para procesar los datos después del gunzipcomando. De hecho, dependiendo de lo que realmente sean los datos y de lo que se necesita hacer con ellos, incluso puede ser posible procesarlos sin guardarlos en el archivo:

while IFS= read -r name; do
    zcat "$name" | process_data
done <file.list

(donde process_datahay alguna canalización que lee los datos sin comprimir de la entrada estándar)

Si el procesamiento de los datos lleva más tiempo que descomprimirlos, la cuestión de si un bucle es más eficiente o no se vuelve irrelevante.

Idealmente , preferiría no trabajar en una lista de nombres de archivo, y en su lugar usar un patrón global de nombre de archivo, como en

for name in ./*.gz; do
    # processing of "$name" here
done

donde ./*.gzhay algún patrón que coincida con los archivos relevantes. De esta manera, no dependemos del número de archivos ni de los caracteres utilizados en los nombres de archivo (pueden contener líneas nuevas u otros caracteres de espacio en blanco, o comenzar con guiones, etc.)

Relacionado:

Kusalananda
fuente
5

De esos dos, gzipes probable que el que tiene todos los archivos pasados ​​a una sola invocación sea ​​más rápido, exactamente porque solo necesita iniciarlo gzipuna vez. (Es decir, si el comando funciona, vea las otras respuestas para las advertencias).

Pero me gustaría recordar la regla de oro de la optimización : no lo haga prematuramente.

  1. No optimices ese tipo de cosas antes de saber que es un problema.

    ¿Esta parte del programa lleva mucho tiempo? Bueno, descomprimir archivos grandes podría, y tendrás que hacerlo de todos modos, por lo que podría no ser tan fácil de responder.

  2. Medida. Realmente, es la mejor manera de estar seguro.

    Verá los resultados con sus propios ojos (o con su propio cronómetro), y se aplicarán a su situación que las respuestas aleatorias en Internet podrían no tener. Pon ambas variantes en scripts y ejecuta time script1.sh, y time script2.sh. (Haga eso con una lista de archivos comprimidos vacíos para medir la cantidad absoluta de los gastos generales).

ilkkachu
fuente
0

¿Qué tan rápido es tu disco?

Esto debería usar todas tus CPU:

parallel -X gzip -d :::: large_file_list

Por lo tanto, es probable que su límite sea la velocidad de su disco.

Puedes intentar ajustarte con -j:

parallel -j50% -X gzip -d :::: large_file_list

Esto ejecutará la mitad de los trabajos en paralelo como el comando anterior, y estresará menos su disco, por lo que, dependiendo de su disco, esto puede ser más rápido.

Ole Tange
fuente