Guión Bash; optimización de la velocidad de procesamiento

10

Me preguntaba si hay pautas generales para optimizar los scripts de Bash.

  • Por ejemplo, es más conveniente escribir bucles que líneas de comandos, pero ¿también es más rápido procesar para el sistema? Ejemplo:

    for i in a b c; do echo $i; done
    
    echo a
    echo b
    echo c
    
  • A veces las personas presentan diferentes soluciones para el mismo problema. Por ejemplo, sed, cut, awk, y echoson todos capaces de despojar a los dígitos de una cadena. Me preguntaba si puede decir que cuanto menos código de dígitos tenga, más rápido será si usa:

    1. el mismo comando, por ejemplo

      STRING=abc.def
      echo ${STRING} | sed 's/.def//g'
      echo ${STRING} | sed '$s/....$//'
      
    2. diferentes comandos, por ejemplo

      STRING=abc.def
      echo ${STRING} | cut -d . -f 1
      echo ${STRING} | sed 's/.def//g'
      
Vincent
fuente
2
En la mayoría de los casos, preferiría la legibilidad a la velocidad.
Bernhard
1
a) Sí, hay una sobrecarga en el uso de un bucle for, pero no importa b) intente minimizar los comandos externos y simplemente reemplace las cosas desde bash. Y si tiene que optimizar su script de shell, está haciendo algo mal y debería considerar usar un lenguaje con mejor soporte de perfilador.
Ulrich Dangel
Las expresiones regulares son generalmente menos eficientes en cuanto al rendimiento que cualquier otra opción, por lo que si ve una manera de hacer algo sin una (por ejemplo, cortar), úsela. Por supuesto, eso puede afectar la legibilidad si su capacidad de lectura está limitada a expresiones regulares;)
goldilocks

Respuestas:

8

Los shells no reorganizan el código que reciben, solo se interpreta una línea tras otra (nada más tiene sentido en un intérprete de comandos). Gran parte del tiempo dedicado por el shell se destina al análisis léxico / análisis / lanzamiento de los programas llamados.

Para operaciones simples (como las que combinan cadenas en los ejemplos al final de la pregunta), me sorprendería si el momento de cargar los programas no abruma las diferencias de velocidad minúsculas.

La moraleja de la historia es que si realmente necesita más velocidad, es mejor usar un lenguaje (semi) compilado como Perl o Python, que es más rápido para comenzar, en el que puede escribir muchas de las operaciones mencionadas directamente y no tiene que llamar a programas externos, y tiene la opción de invocar programas externos o llamar a módulos C (o lo que sea) optimizados para hacer gran parte del trabajo. Esa es la razón por la cual en Fedora el "azúcar de administración del sistema" (esencialmente las GUI) están escritas en Python: puede agregar una buena GUI sin demasiado esfuerzo, lo suficientemente rápido para tales aplicaciones, tener acceso directo a las llamadas del sistema. Si eso no es suficiente velocidad, toma C ++ o C.

Pero no vaya allí, a menos que pueda demostrar que la ganancia de rendimiento vale la pérdida de flexibilidad y tiempo de desarrollo. Los scripts de Shell no son demasiado malos para leer, pero me estremezco cuando recuerdo algunos scripts utilizados para instalar Ultrix que una vez intenté descifrar. Me di por vencido, se había aplicado demasiada "optimización de script de shell".

vonbrand
fuente
1
+1, pero mucha gente argumentaría que es más probable que haya una ganancia en flexibilidad y tiempo de desarrollo usando algo como Python o Perl vs. Shell, no una pérdida. Yo diría que solo use un script de shell si es necesario, o lo que está haciendo implica una gran cantidad de comandos específicos de shell.
Ricitos de Oro
22

La primera regla de optimización es: no optimizar . Prueba primero. Si las pruebas muestran que su programa es demasiado lento, busque posibles optimizaciones.

La única forma de estar seguro es comparar su caso de uso. Existen algunas reglas generales, pero solo se aplican a volúmenes de datos típicos en aplicaciones típicas.

Algunas reglas generales que pueden o no ser ciertas en cualquier circunstancia particular:

  • Para el procesamiento interno en el shell, ATT ksh es el más rápido. Si realiza muchas manipulaciones de cadenas, use ATT ksh. Dash viene segundo; bash, pdksh y zsh se quedan atrás.
  • Si necesita invocar un shell con frecuencia para realizar una tarea muy corta cada vez, el guión gana debido a su bajo tiempo de inicio.
  • Iniciar un proceso externo cuesta tiempo, por lo que es más rápido tener una tubería con piezas complejas que una tubería en un bucle.
  • echo $fooes más lento que echo "$foo", porque sin comillas dobles, se divide $fooen palabras e interpreta cada palabra como un patrón comodín de nombre de archivo. Más importante aún, rara vez se desea ese comportamiento de división y engrosamiento. Por lo tanto, recuerde siempre poner comillas dobles alrededor de sustituciones variables y sustituciones de comandos: "$foo", "$(foo)".
  • Las herramientas dedicadas tienden a ganarse a las herramientas de uso general. Por ejemplo, herramientas como cuto headpueden ser emuladas con sed, pero sedserán más lentas e awkincluso más lentas. El procesamiento de cadenas de shell es lento, pero para cadenas cortas supera en gran medida la llamada a un programa externo.
  • Los lenguajes más avanzados como Perl, Python y Ruby a menudo le permiten escribir algoritmos más rápidos, pero tienen un tiempo de inicio significativamente mayor, por lo que solo valen la pena por el rendimiento de grandes cantidades de datos.
  • Al menos en Linux, las canalizaciones tienden a ser más rápidas que los archivos temporales.
  • La mayoría de los usos de las secuencias de comandos de shell están relacionados con procesos vinculados a E / S, por lo que el consumo de CPU no importa.

Es raro que el rendimiento sea una preocupación en los scripts de shell. La lista anterior es puramente indicativa; está perfectamente bien usar métodos "lentos" en la mayoría de los casos, ya que la diferencia es a menudo una fracción de un porcentaje.

Por lo general, el objetivo de un script de shell es hacer algo rápido. Tienes que ganar mucho con la optimización para justificar pasar más minutos escribiendo el guión.

Gilles 'SO- deja de ser malvado'
fuente
2
Si bien pythony rubydefinitivamente son más lentos para comenzar, al menos en mi sistema, perles tan rápido como basho ksh. GNU awk es significativamente más lento que GNU sed especialmente en entornos locales utf-8, pero no es cierto para todos los awks y todos los seds. ksh93> dash> pdksh> zsh> bash no siempre es tan claro como eso. Algunos proyectiles son mejores en algunas cosas que otros, y el ganador no siempre es el mismo.
Stéphane Chazelas
2
Re "tienes que ganar mucho de ..." : si "tú" incluye la base de usuarios, es cierto. Con los scripts de shell en los paquetes populares de Linux, a menudo los usuarios desperdician colectivamente varias órdenes de magnitud más tiempo del que ahorra el programador apresurado.
agc
2

Ampliaremos aquí nuestro ejemplo global para ilustrar algunas características de rendimiento del intérprete de script de shell. Comparando el bashe dashintérpretes para este ejemplo en el que un proceso se genera para cada uno de 30.000 archivos, muestra que el tablero puede fork los wcprocesos de casi el doble de rápido quebash

bash-4.2$ time dash -c 'for i in *; do wc -l "$i"; done>/dev/null'
real    0m1.238s
user    0m0.309s
sys     0m0.815s


bash-4.2$ time bash -c 'for i in *; do wc -l "$i"; done>/dev/null'
real    0m1.422s
user    0m0.349s
sys     0m0.940s

¡Comparar la velocidad de bucle base al no invocar los wcprocesos, muestra que el bucle del tablero es casi 6 veces más rápido!

$ time bash -c 'for i in *; do echo "$i">/dev/null; done'
real    0m1.715s
user    0m1.459s
sys     0m0.252s



$ time dash -c 'for i in *; do echo "$i">/dev/null; done'
real    0m0.375s
user    0m0.169s
sys     0m0.203s

El bucle todavía es relativamente lento en cualquiera de los shell como se demostró anteriormente, por lo que para la escalabilidad deberíamos intentar usar técnicas más funcionales para que la iteración se realice en procesos compilados.

$ time find -type f -print0 | wc -l --files0-from=- | tail -n1
    30000 total
real    0m0.299s
user    0m0.072s
sys     0m0.221s

Lo anterior es, con mucho, la solución más eficiente e ilustra bien el punto de que uno debe hacer lo menos posible en el script de shell y apuntar solo a usarlo para conectar la lógica existente disponible en el rico conjunto de utilidades disponibles en un sistema UNIX.

Errores robados del script de shell común por Pádraig Brady.

Rahul Patil
fuente
1
Una regla genérica: el manejo del descriptor de archivos también cuesta, así que reduzca su conteo. En lugar de for i in *; do wc -l "$i">/dev/null; donehacerlo mejor for i in *; do wc -l "$i"; done>/dev/null.
manatwork
@manatwork también dará salida nula de timecmd
Rahul Patil
@manatwork Bien ... ahora Por favor, también dame una salida sin invocar wc -l, comprueba que he actualizado en publicar tu salida
Rahul Patil
Bueno, las mediciones anteriores se hicieron en un directorio más pequeño. Ahora creé uno con 30000 archivos y repetí las pruebas: pastebin.com/pCV6QKp2
manatwork
Esos puntos de referencia no permiten los diferentes tiempos de inicio de cada shell. Puntos de Referencia hecho de dentro de cada carcasa sería mejor.
agc