Performances de sed

Daniel Caillibaud ml.dcailli at free.fr
Tue Sep 2 21:43:44 CEST 2008


Le 02/09/08 à 17:38, Stephane Jourdois <kwisatz-shell at rubis.org> a écrit :

> On Mon, Sep 01, 2008 at 03:06:53AM +0200, Daniel Caillibaud wrote:
> > Bonjour,
> > 
> > Je me suis écrit la petite fonction suivante (en bash) pour découper un dump mysql par tables (je rajoute ici des
> > retours chariot pour la lisibilité).

> Je répond un peu à l'arrache parce que je n'ai pas le temps de te faire
> une réponse complète mais voilà quelques idées :

Pour une réponse à l'arache, je la trouve déjà très complète ;-)

> 1) première règle quand tu fais du parsage, tu t'arranges pour ne parser
> qu'une seule fois (toi tu parses <nombre de tables>+1 fois, c'est ultra
> dégueulasse, et évidemment les perfs s'en ressentent (sans parler de la
> scalabilité en O(n) lors que tu pourrais faire du O(1)).

Oui, évidemment, c'est relativement crade, mais mon but était de découper rapidement mon dump en fichiers plus petits. Je ne
m'attendais pas à un truc super rapide mais j'étais quand même étonné que cela n'aille pas plus vite.

Le 1er parsing de awk (pour trouver les n° de lignes où couper) est "relativement" rapide, en tout cas bien suffisant pour
cette utilisation (2-3s), mais ensuite je pensais que sed irait plus vite lors de chaque

sed -ne '22,50 p; 51 q;' < "test.sql" > prefix_access.sql
sed -ne '51,704 p; 705 q;' < "test.sql" > prefix_accesslog.sql

Même avec les n° de ligne, il parse le début du fichier, mais ensuite il s'arrête à la dernière ligne voulue (et ne parse donc
pas la fin). Donc normalement, sed parse le début du fichier 100x.

> Le corollaire est la 2ème règle, qui est encore plus importante :
> 
> 2) deuxième règle du parsage, tu parses en temps réel dès que c'est
> possible, i.e. tu fais comme si tu parsais un stream, et tu fournis les
> réponses asap.

Sur le principe je comprend, mais je n'ai pas eu d'idée lumineuse pour l'appliquer à ce cas (je ne modifie pas les lignes mais
les écrit dans un fichier différent suivant leur position).
sed peut écrire dans un fichier (avec w) mais je ne vois pas comment rendre le nom du fichier variable (même en utilisant le
holdspace), et il ne me semble pas que awk puisse écrire dans un fichier variable.

> Je m'explique :
> 
> - gcc prend un fichier, le parse, puis fait sa sauce. Il met tout le
>   fichier en ram, puis commence à travailler, puis une fois qu'il a fini
> il sort ce qu'il veut. Un peu comme tu as fait, sauf que toi tu as fait
> un for (i=0; i < 100; i++) autour de l'appel :-)
> 
> - sort parse un stream, mais il ne commence à printer qu'une fois qu'il
>   a tout le stream en ram. évidemment c'est mieux mais toujours pas top.
> 
> - cat, lui, il parse un stream et en plus il affiche au fûr et à mesure
>   (modulo le cache éventuel sur stdout), c'est le must du parsage. En
> général, les scripts sed/awk fonctionnent comme ça (par ligne, donc).
> 
> 
> Donc pour finir, la bonne solution : tu écris un bête script (en sed,
> awk, perl ou ce que tu veux) qui lit une ligne, réalise un traitement
> sur cette ligne (en particulier un traitement dépendant de l'état, et un
> traitement qui peut changer l'état), puis tu réitère jusqu'à la fin. Pas
> de stockage nulle part, et un seul parsage.

Ça m'a donné une idée avec un seul passage sed qui insère des commandes cat dans le dump, qu'on exécute ensuite (sur 1 ligne) :

sed -e '/^--/ d; /^\/\*!/d; /^$/d; s/`//g; s/^DROP TABLE IF EXISTS \([^;]*\)/marqueur-de-fin-de-split\ncat > \1.sql
<<marqueur-de-fin-de-split\nDROP TABLE IF EXISTS \1/;' < dump.sql |sh

(il y a juste un marqueur en trop en 1re ligne et un qui manque en dernière ligne, mais ça marche)

Ça va un peu plus vite que mon truc bourrin de l'autre jour, mais pas tant que ça :
sur un dump de 100 tables et 22Mo, sed + sh prend 21s (6s + 15s) et la solution awk qui génère des commandes sed + éxécution par
le shell 26s.

> Pour ton cas, et en perl, ça donne (écrit dans le mail donc non testé,
> désolé :-)) :

C'est déjà sympatique de donner du code commenté !
Vu ma connaissance symbolique de perl, j'avais éliminé d'office cette solution ;-)

> #!/usr/bin/perl
> use strict;
> use warnings;
> 
> my $db = undef; # Current database name
> my $table = undef; # Current table name
> my $fh = undef; # Current filehandle
> while (<>) {
> 	if (/^CREATE DATABASE .* `([^`]+)`/) {
> 		$db = $1;
> 	} elsif (/^DROP TABLE IF EXISTS `([^`]+)`/) {
> 		$table = $1
> 		close $fh if $fh; # pas nécessaire vu que le open
> 				  # suivant ferme automatiquement
> 				  # le filehandle.
> 		open $fh, '>', "$db.$table.sql";
> 	}
> 
> 	if ($db and $table) {
> 		print $fh $_ if $fh;
> 	}
> }
> close $fh;
> -----
> 
> Je ne suis pas sûr que ça donne strictement le même résultat que ton
> script vu sa complexité, mais bon ça me semble être un début pour toi.

Ton script met 0.351s ;-)

Bon ben, il me reste à apprendre perl si je veux jouer dans la même cour...

Daniel

PS : Le script modifié utilisé (j'ai pas de CREATE DATABASE dans mes dump donc j'ai viré les ref à $db, ajouté un ;
manquant et viré la ligne close qui lui plaisait pas)
-----
#!/usr/bin/perl
use strict;
use warnings;

my $table = undef; # Current table name
my $fh = undef; # Current filehandle

while (<>) {
	if (/^DROP TABLE IF EXISTS `([^`]+)`/) {
		$table = $1;
		open $fh, '>', "perl_$table.sql";
	}

	if ($table) {
		print $fh $_ if $fh;
	}
}
close $fh;
-----


More information about the Shell mailing list