Les scripts shell c'est lent

Salut,

Ce fil ne pose pas de question. Je voulais juste partager avec vous un p’tit bout d’histoire que nous avons eu au boulot.

Je travail sur un bus applicatif. Pour faire simple on reçois des fichiers via différents protocoles et on les envoies à différents destinataire avec éventuellement un peu de traitement sur ces fichiers.

Tous les fichiers qui arrivent passe par un script bash qui fait appel à inotifywait pour savoir quel fichiers il doit traiter.

La première version de ce script était assez naïve. Elle fait une boucle sur inotifywait et traite le fichier qui viens d’arriver avant d’attendre un nouvel évènement. C’est très mauvais d’un point de vu performance. Dès que l’on monte un peu en charge, le traitement d’un fichier devient trop long. Il faut savoir que le noyau maintient une liste d’évènements inotify. Si on est trop lent pour traiter les évènements ils s’accumule, jusqu’à atteindre une limite de 16 000 et quelques évènements. À ce moment là si le noyau reçois encore une évènement, il va ajouter un évènement pour dire qu’il a un overflow. Bref niveau perf on était pas terrible.

La grosse optimisation a était de créer des « workers », on garde un processus principal qui attend les évènements et il transmet chaque évènement à un worker qui lui fait le boulot. Pour faire ça c’est très simple, il suffit d’utiliser xargs. Nous avons décidé de ne pas utiliser de pipe pour la communication entre le processus principal et xargs car les pipes sont limités en taille (4kio si ma mémoire est bonne). On utilise donc un fichier et on fait une rotation régulière de ce dernier.

Une fois fait on a commencé à avoir des performances décentes sous forte charge mais on a remarqué via time que la quasi totalité du temps était passé en espace noyau. Ça vient entre autre du nombre de fork que lance chaque worker. Pour résoudre ça nous avons utilisé tout ce que je connais comme raffinements pour éviter l’usage de grep, sed, cur, awk etc. On a gagné 50% de performance avec ça.

Il reste quelques fork aux quels on ne peut pas échapper en bash comme curl et surtout des appels un peu trop régulier à la commande date (pour le logging). Bash 4.2 offre une super solution pour ça prinf permet d’utiliser un formateur de date au quel on peut passer -1 pour indiquer la date courante. Mais nous utilisons bash 4.1 donc on ne peut rien faire ainsi. Une solution (pas encore mis en place) serait de faire faire ces appels par un autre processus qui loggerait pour tous les autres (et qui ferrais entre autre appels à date¹), mais ce n’est pas encore mis en place.

Bref tout ça pour dire que ce n’est pas parce que l’on fait des script shell que le script et forcément lent. Ici on a mis en place un paterne multitâche en très peu de temps et très simplement et quand on commence à regarder les performance sous forte charge les fork commencent à prendre vraiment beaucoup (trop) de temps.

¹ : l’intérêt c’est que le logging ne deviens plus bloquant

logger message

:117

[quote=“haleth”] logger message[/quote]
C’est pas un builtin ça change rien d’un point de vu performance (en vrai si c’est un chouïa plus rapide, mais c’est quantité négligeable face au fork que tu fait).

J’aime bien le bash pour mes bricoles perso, là je m’amuse à programmer un “moteur” de forum ultra-minimaliste (donc en Bash sous forme de CGI pour serveur web) :033

Sinon, j’imagine que le Perl, tout en étant assez proche du shell, est plus rapide que ce dernier?

Et pas qu’un peu. Mais son intérêt, comme celui de python, dans notre cas d’usage aurait était limité. En effet aucun de ces deux là ne gèrent de vrai parallélisme (l’interpreteur ne sait pas exécuter 2 threads en même temps) du coup nous aurions tout de même du passer par xargs.

Bon en vrai, j’ai pas eu mon mot à dire, sinon je serait probablement partie sur du perl pour avoir un vrai langage et des perf un peu meilleur. Mais je crois que mes collègues ont peur de mes choix depuis qu’ils voient que je code en awk et en pure zsh (zsh aussi aurait permis de virer probablement tous les fork()).