Script bash; optimisation de la vitesse de traitement

10

Je me demandais s'il y avait des directives générales pour optimiser les scripts Bash.

  • Par exemple, il est plus pratique d'écrire des boucles que des lignes de commandes, mais est-il également plus rapide à traiter pour le système? Exemple:

    for i in a b c; do echo $i; done
    
    echo a
    echo b
    echo c
  • Parfois, les gens présentent des solutions différentes pour le même problème. Par exemple, sed, cut, awket echosont tous capables de dépouiller les chiffres d'une chaîne. Je me demandais si vous pouvez dire que moins le code contient de chiffres, plus il est rapide si vous utilisez:

    1. la même commande, par exemple

      STRING=abc.def
      echo ${STRING} | sed 's/.def//g'
      echo ${STRING} | sed '$s/....$//'
    2. différentes commandes, par exemple

      STRING=abc.def
      echo ${STRING} | cut -d . -f 1
      echo ${STRING} | sed 's/.def//g'
Vincent
la source
2
Dans la plupart des cas, je préférerais la lisibilité à la vitesse.
Bernhard
1
a) Oui, il y a une surcharge dans l'utilisation d'une boucle for mais cela n'a pas d'importance b) essayez de minimiser les commandes externes et remplacez simplement les choses depuis bash. Et si vous devez optimiser votre script shell, vous faites quelque chose de mal et devriez envisager d'utiliser un langage avec un meilleur support du profileur.
Ulrich Dangel
Les expressions régulières sont généralement moins efficaces en termes de performances que tout autre choix, donc si vous voyez un moyen de faire quelque chose sans (par exemple, couper), utilisez-le. Bien sûr, cela peut affecter la lisibilité si votre capacité de lecture est limitée aux expressions régulières;)
goldilocks

Réponses:

8

Les shells ne réorganisent pas le code qu'ils reçoivent, il est simplement interprété ligne par ligne (rien d'autre n'a beaucoup de sens dans un interpréteur de commandes). Une grande partie du temps passé par le shell est consacrée à l'analyse / l'analyse syntaxique / le lancement des programmes appelés.

Pour les opérations simples (comme celles qui montent des chaînes dans les exemples à la fin de la question), je serais surpris si le temps de chargement des programmes ne submerge aucune minuscule différence de vitesse.

La morale de l'histoire est que si vous avez vraiment besoin de plus de vitesse, il vaut mieux utiliser un langage (semi) compilé comme Perl ou Python, qui est plus rapide à exécuter pour commencer, dans lequel vous pouvez écrire de nombreuses opérations mentionnées directement et ne pas avoir à appeler des programmes externes, et a la possibilité d'appeler des programmes externes ou d'appeler dans des modules C optimisés (ou quoi que ce soit) pour faire une grande partie du travail. C'est la raison pour laquelle dans Fedora le "sucre d'administration système" (GUI, essentiellement) est écrit en Python: peut ajouter une interface graphique agréable sans trop d'effort, assez rapide pour de telles applications, avoir un accès direct aux appels système. Si ce n'est pas assez de vitesse, prenez C ++ ou C.

Mais n'y allez pas , sauf si vous pouvez prouver que le gain de performances vaut la perte de flexibilité et de temps de développement. Les scripts shell ne sont pas trop mauvais à lire, mais je frémis quand je me souviens de certains scripts utilisés pour installer Ultrix que j'ai essayé de déchiffrer. J'ai abandonné, trop d '"optimisation du script shell" avait été appliquée.

vonbrand
la source
1
+1 mais beaucoup de gens diraient qu'il y a plus de chances de gagner en flexibilité et en temps de développement en utilisant quelque chose comme python ou perl vs shell, pas une perte. Je dirais que n'utilisez un script shell que si cela est nécessaire, ou ce que vous faites implique une quantité abondante de commandes spécifiques au shell.
goldilocks
22

La première règle d'optimisation est: ne pas optimiser . Testez d'abord. Si les tests montrent que votre programme est trop lent, recherchez les optimisations possibles.

La seule façon d'être sûr est de comparer votre cas d'utilisation. Il existe des règles générales, mais elles ne s'appliquent qu'aux volumes de données typiques dans les applications typiques.

Quelques règles générales qui peuvent ou non être vraies dans une circonstance particulière:

  • Pour le traitement interne dans le shell, ATT ksh est le plus rapide. Si vous faites beaucoup de manipulations de chaînes, utilisez ATT ksh. Dash vient en deuxième position; bash, pdksh et zsh sont en retard.
  • Si vous devez invoquer un shell fréquemment pour effectuer une tâche très courte à chaque fois, le tiret gagne en raison de son temps de démarrage faible.
  • Le démarrage d'un processus externe coûte du temps, il est donc plus rapide d'avoir un pipeline avec des pièces complexes qu'un pipeline en boucle.
  • echo $fooest plus lent que echo "$foo", car sans guillemets, il se divise $fooen mots et interprète chaque mot comme un motif générique de nom de fichier. Plus important encore, ce comportement de division et de globulation est rarement souhaité. N'oubliez donc pas de toujours mettre des guillemets autour des substitutions de variables et des substitutions de commandes: "$foo", "$(foo)".
  • Les outils dédiés ont tendance à séduire les outils à usage général. Par exemple, des outils comme cutou headpeuvent être émulés avec sed, mais sedseront plus lents et awkseront encore plus lents. Le traitement des chaînes de shell est lent, mais pour les chaînes courtes, il bat largement l'appel d'un programme externe.
  • Les langages plus avancés tels que Perl, Python et Ruby vous permettent souvent d'écrire des algorithmes plus rapides, mais ils ont un temps de démarrage considérablement plus élevé, ils ne valent donc que pour les performances pour de grandes quantités de données.
  • Sous Linux au moins, les canaux ont tendance à être plus rapides que les fichiers temporaires.
  • La plupart des utilisations des scripts shell concernent des processus liés aux E / S, donc la consommation du processeur n'a pas d'importance.

Il est rare que les performances soient un problème dans les scripts shell. La liste ci-dessus est purement indicative; il est parfaitement possible d'utiliser des méthodes «lentes» dans la plupart des cas, car la différence est souvent d'une fraction de pour cent.

Habituellement, le but d'un script shell est de faire quelque chose rapidement. Vous devez gagner beaucoup à l'optimisation pour justifier de passer des minutes supplémentaires à écrire le script.

Gilles 'SO- arrête d'être méchant'
la source
2
Alors que pythonet rubysont certainement plus lents à démarrer, au moins sur mon système, il perlest aussi rapide à démarrer que bashou ksh. GNU awk est beaucoup plus lent que GNU sed, en particulier dans les locales utf-8, mais ce n'est pas le cas pour tous les awks et tous les seds. le ksh93> dash> pdksh> zsh> bash n'est pas toujours aussi clair que ça. Certains obus sont meilleurs à certains égards que d'autres, et le gagnant n'est pas toujours le même.
Stéphane Chazelas
2
Re "vous devez gagner beaucoup de ..." : si "vous" inclut la base d'utilisateurs, c'est vrai. Avec les scripts shell dans les packages Linux populaires, les utilisateurs perdent collectivement plusieurs ordres de grandeur plus de temps que le programmeur précipité n'en économise.
agc
2

Nous développerons ici notre exemple de globalisation ci-dessus pour illustrer certaines caractéristiques de performance de l'interpréteur de script shell. La comparaison des interprètes bashet dashde cet exemple où un processus est généré pour chacun des 30 000 fichiers montre que le tableau de bord peut bifurquer les wcprocessus presque deux fois plus vite 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

La comparaison de la vitesse de bouclage de base en n'invoquant pas les wcprocessus montre que le bouclage du tiret est presque 6 fois plus rapide!

$ 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

Le bouclage est encore relativement lent dans l'un ou l'autre shell comme démontré précédemment, donc pour l'évolutivité, nous devrions essayer d'utiliser des techniques plus fonctionnelles afin que l'itération soit effectuée dans les processus compilés.

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

Ce qui précède est de loin la solution la plus efficace et illustre bien le fait que l'on devrait faire le moins possible dans un script shell et viser simplement à l'utiliser pour connecter la logique existante disponible dans le riche ensemble d'utilitaires disponibles sur un système UNIX.

Volé à partir d' erreurs courantes de script shell par Pádraig Brady.

Rahul Patil
la source
1
Une règle générique: la gestion des descripteurs de fichiers coûte également, donc réduisez leur nombre. Au lieu de for i in *; do wc -l "$i">/dev/null; donemieux faire for i in *; do wc -l "$i"; done>/dev/null.
manatwork
@manatwork, il annulera également la sortie de timecmd
Rahul Patil
@manatwork Bon ... maintenant Veuillez également me donner la sortie de sans invoquer wc -l, vérifiez que j'ai mis à jour en poste votre sortie
Rahul Patil
Eh bien, les mesures précédentes ont été faites sur un répertoire plus petit. Maintenant, j'en ai créé un avec 30000 fichiers et répété les tests: pastebin.com/pCV6QKp2
manatwork
Ces repères ne permettent pas de prendre en compte les différentes heures de démarrage de chaque shell. Les repères effectués à partir de chaque shell seraient mieux.
agc