Bash while read loop es extremadamente lento en comparación con cat, ¿por qué?

Resuelto David Parks asked hace 11 años • 4 respuestas

Un script de prueba simple aquí:

while read LINE; do
        LINECOUNT=$(($LINECOUNT+1))
        if [[ $(($LINECOUNT % 1000)) -eq 0 ]]; then echo $LINECOUNT; fi
done

Cuando lo hago, cat my450klinefile.txt | myscriptla CPU se bloquea al 100% y puede procesar alrededor de 1000 líneas por segundo. Unos 5 minutos para procesar lo que cat my450klinefile.txt >/dev/nullse hace en medio segundo.

¿Existe una forma más eficiente de hacer esencialmente esto? Solo necesito leer una línea de la entrada estándar, contar los bytes y escribirla en una canalización con nombre. Pero la velocidad incluso de este ejemplo es increíblemente lenta.

Cada 1 Gb de líneas de entrada necesito realizar algunas acciones de secuencias de comandos más complejas (cerrar y abrir algunas tuberías a las que se alimentan los datos).

David Parks avatar Dec 07 '12 18:12 David Parks
Aceptado

La razón while reades tan lenta es que se requiere que el shell realice una llamada al sistema por cada byte. No puede leer un búfer grande de la tubería, porque el shell no debe leer más de una línea del flujo de entrada y, por lo tanto, debe comparar cada carácter con una nueva línea. Si ejecuta straceen un while readbucle, puede ver este comportamiento. Este comportamiento es deseable porque permite hacer de manera confiable cosas como:

while read size; do test "$size" -gt 0 || break; dd bs="$size" count=1 of=file$(( i++ )); done

en el que los comandos dentro del bucle se leen desde la misma secuencia que lee el shell. Si el shell consumiera una gran cantidad de datos leyendo grandes buffers, los comandos internos no tendrían acceso a esos datos. Un efecto secundario desafortunado es que reades absurdamente lento.

William Pursell avatar Dec 07 '2012 13:12 William Pursell

En este caso , es porque el bashguión se interpreta y no está realmente optimizado para la velocidad. Por lo general, será mejor utilizar una de las herramientas externas, como:

awk 'NR%1000==0{print}' inputFile

que coincide con su muestra de "imprimir cada 1000 líneas".

Si desea (para cada línea) generar el recuento de líneas en caracteres seguidos de la línea misma y canalizarlo a través de otro proceso, también puede hacerlo:

awk '{print length($0)" "$0}' inputFile | someOtherProcess

Herramientas como awk, sed, grepy cutlas más poderosas perlson mucho más adecuadas para estas tareas que un script de shell interpretado.

paxdiablo avatar Dec 07 '2012 12:12 paxdiablo

La solución Perl para contar bytes de cada cadena:

perl -p -e '
use Encode;
print length(Encode::encode_utf8($_))."\n";$_=""' 

Por ejemplo:

dd if=/dev/urandom bs=1M count=100 |
   perl -p -e 'use Encode;print length(Encode::encode_utf8($_))."\n";$_=""' |
   tail

me funciona como 7.7Mb/s

para comparar la cantidad de script utilizado:

dd if=/dev/urandom bs=1M count=100 >/dev/null

funciona a 9,1 Mb/s

Parece que el guión no es tan lento :)

zb' avatar Dec 07 '2012 12:12 zb'