Pages

mercredi 29 mai 2013

optimisation de script bash en limitant l'ouverture de processus

Ce qui prend du temps pour un script shell est le nombre de programmes appelés et donc, le nombre de processus créés.

Le meilleur moyen d’accélérer un script est de passer par les builtins et limiter les boucles ouverture/fermeture de programme.
En fait, dans certain cas, on pourrait avoir une commande qui lit sur l'entrée standard et retourne un résultat ; un peu comme bc. C'est là que les coprocess viennent à la rescousse :).

Un coprocess est un sous-shell exécuté de façon asynchrone et fournissant les tubes d'entrée/sortie. Ceux-ci sont accessibles via COPROC[1] et COPROC[2].

Dû coup, avec bc, la méthode est d'écrire dans un tube et lire dans l'autre. Comme la lecture est bloquante, le script va attendre que bc retourne le résultat.
Et voilà l'ouverture d'un programme remplacé par une lecture/écriture.

Par exemple, un petit programme qui lit un fichier contenant des opérations mathématiques et les affiches suivies du résultat.

#!/bin/bash
[ -z "$1" ] && echo $0: file >&2 && exit 1

#coprocess de bc avec la sortie d'erreur redirigée vers la sortie standard
coproc bc 2>&1

while read l; do
 echo "$l" >&${COPROC[1]} # écrire dans le coprocess
 read result <&${COPROC[0]} # réception du résultat
 echo $l = $result
done < "$1"

Sans coproc, la boucle est plus simple mais un processus sera ouvert pour chaque ligne du fichier, ce qui est particulièrement coûteux.

#sans coproc
while read l; do
 echo "$l" = `echo "$l" | bc 2>&1`
done < "$1"

À tester avec par exemple un fichier de calcul comme celui-ci

2+3
2+3*9
23*9
23s*9
3*9

mardi 21 mai 2013

printf vs cout vs boost::format vs tinyformat

Après diverses recherches et comparaisons entre printf et cout, j'ai décidé de faire mon propre benchmark en prenant en compte:

  • La norme utilisée (c++03, c++11)
  • La synchronisation des flux c++ avec ios_base::sync_with_stdio().
  • Le multithreading (option -pthread et inclusion de <thread> ou <pthread.h>).

J'en profite également pour tester et comparer avec boost::format et tinyformat.

Mes tests sont minimalistes et ne couvrent qu'une très petite partie des possibilités mais:

  • La norme utilisée et les threads n'influencent pas les résultats
  • Le fait de désynchroniser cout des streams C fait gagner en performance et ce de façon significative pour certains types.
  • Les performances varient selon le type. Par exemple, les flottants sont plus lents avec les flux c++ mais c'est l’inverse pour les caractères ou les chaînes.
  • boost::format est très lent mais possède l'avantage (ou pas) de mettre une à une les variables à formater. À vrai dire je n'ai pas regardé ce que proposait cette lib.
  • tinyformat est un peu plus lent que les flux C++ (tout dépend du format) mais est une alternative à printf. Tinyformat étant basé sur les flux du C++ la surcharge des flux fonctionne d'office.

Au final la différence entre printf et cout n'est pas importante et varie selon les types de variable, ce qui en rend délicat les mesures.
Cependant, l’avantage d'ostream est la surchargeable qui rend aisée l'ajout de nouveau type à écrire.
Et l'avantage de printf est la possibilité de fournir un format ce qui permet un meilleur contrôle dans l'affichage (ordre des paramètres, internationalisation, ...). Défaut de ostream qui peut être pallié via des libs spécialisées comme tinyformat ou boost::format.

Ci-dessous un tableau un peu brut qui donne des résultats en secondes de la médiane de 10 tests.
Plus lisible ici et mieux étaler ici. Pour finir, des graphiques en bars.
Code source est données: printf_vs_cout.tar.gz (~151 Ko) ou printf_vs_cout.zip (~223 Ko).

Comparaison (en secondes) de différentes APIs de manipulation de flux
format ostream printf boost::format tinyformat
format ostream printf boost::format tinyformat
cpp03
'%d' 0.078679 0.108698 0.464650 0.142462
'%d\n' 0.105090 0.113880 0.489037 0.159989
'%f' 0.463047 0.271212 0.993986 0.494874
'%f\n' 0.501310 0.278252 0.998719 0.505266
'%+6.4f\n' 0.437482 0.264044 0.999680 0.474117
'%c' 0.032680 0.010411 0.423434 0.101393
'%c\n' 0.065180 0.067260 0.444380 0.118883
'%s (chaîne constante)' 0.033918 0.022545 0.473357 0.107968
'%s (chaîne constante)\n' 0.034260 0.022663 0.474171 0.108528
'%s' 0.038505 0.102462 0.442303 0.112725
'%s\n' 0.038247 0.100166 0.440523 0.112081
'%20s\n' 0.199725 0.130075 0.478601 0.289536
'%d blah blah: %s%f\n' 0.637055 0.415993 2.256252 0.762320
cpp03.no_sync
'%d' 0.067002 0.109601 0.476751 0.147007
'%d\n' 0.100137 0.115022 0.492545 0.163944
'%f' 0.397226 0.271981 1.026970 0.503956
'%f\n' 0.441220 0.277866 1.043543 0.514723
'%+6.4f\n' 0.397340 0.262398 0.991949 0.493908
'%c' 0.025976 0.010431 0.447651 0.107648
'%c\n' 0.053353 0.067874 0.457830 0.114286
'%s (chaîne constante)' 0.027245 0.022667 0.457423 0.111958
'%s (chaîne constante)\n' 0.027467 0.022671 0.454262 0.112207
'%s' 0.035850 0.101713 0.450900 0.118588
'%s\n' 0.034884 0.100841 0.454078 0.118410
'%20s\n' 0.091354 0.130033 0.493873 0.188529
'%d blah blah: %s%f\n' 0.617792 0.415237 2.232278 0.799673
cpp03.thread
'%d' 0.076335 0.111163 0.483686 0.143798
'%d\n' 0.105287 0.115094 0.496408 0.160223
'%f' 0.417944 0.272958 0.987420 0.496637
'%f\n' 0.461985 0.279903 1.033756 0.510683
'%+6.4f\n' 0.390526 0.263052 0.980551 0.481614
'%c' 0.032338 0.010354 0.440483 0.103588
'%c\n' 0.065121 0.067937 0.455177 0.120682
'%s (chaîne constante)' 0.034495 0.022692 0.482209 0.110065
'%s (chaîne constante)\n' 0.034517 0.022687 0.479707 0.109049
'%s' 0.039811 0.102205 0.458460 0.113883
'%s\n' 0.039298 0.100060 0.455681 0.111497
'%20s\n' 0.198438 0.130106 0.493480 0.286004
'%d blah blah: %s%f\n' 0.614676 0.418298 2.101005 0.796899
cpp03.thread.no_sync
'%d' 0.068209 0.110208 0.484866 0.145173
'%d\n' 0.100651 0.115347 0.501651 0.161192
'%f' 0.380537 0.274508 0.980840 0.495117
'%f\n' 0.423242 0.279306 0.994300 0.512564
'%+6.4f\n' 0.381993 0.263379 0.963846 0.483052
'%c' 0.025535 0.010461 0.451770 0.106700
'%c\n' 0.051531 0.067833 0.461615 0.114352
'%s (chaîne constante)' 0.026337 0.022717 0.467188 0.110280
'%s (chaîne constante)\n' 0.026454 0.022715 0.468853 0.111218
'%s' 0.035278 0.102782 0.449293 0.117769
'%s\n' 0.034852 0.100985 0.454568 0.117806
'%20s\n' 0.089904 0.129334 0.528948 0.190800
'%d blah blah: %s%f\n' 0.604321 0.416814 2.101355 0.814668
cpp11
'%d' 0.076249 0.109847 0.468822 0.143676
'%d\n' 0.105597 0.116130 0.490844 0.159679
'%f' 0.461125 0.270193 1.009484 0.504168
'%f\n' 0.498769 0.277011 0.999355 0.521951
'%+6.4f\n' 0.422417 0.263603 0.951426 0.489394
'%c' 0.032439 0.062792 0.439045 0.101254
'%c\n' 0.064846 0.067247 0.457943 0.118911
'%s (chaîne constante)' 0.034257 0.100141 0.429901 0.108771
'%s (chaîne constante)\n' 0.034244 0.100071 0.423239 0.108540
'%s' 0.037396 0.102511 0.433076 0.109352
'%s\n' 0.040426 0.100593 0.432691 0.111921
'%20s\n' 0.200948 0.129657 0.457259 0.288542
'%d blah blah: %s%f\n' 0.612473 0.414612 2.107274 0.765568
cpp11.no_sync
'%d' 0.066652 0.110400 0.473127 0.146732
'%d\n' 0.100151 0.115625 0.497890 0.161555
'%f' 0.405104 0.271282 1.008395 0.511858
'%f\n' 0.441258 0.278531 1.023761 0.520053
'%+6.4f\n' 0.395064 0.263529 0.994490 0.503324
'%c' 0.027678 0.062949 0.441796 0.105975
'%c\n' 0.053462 0.067833 0.452007 0.113988
'%s (chaîne constante)' 0.030517 0.099791 0.438610 0.116545
'%s (chaîne constante)\n' 0.030432 0.099902 0.452633 0.116247
'%s' 0.032077 0.102299 0.435142 0.114287
'%s\n' 0.034866 0.100304 0.442736 0.117459
'%20s\n' 0.090916 0.130642 0.482727 0.187818
'%d blah blah: %s%f\n' 0.617891 0.416206 2.125498 0.787507
cpp11.thread
'%d' 0.079597 0.111012 0.490221 0.144572
'%d\n' 0.105356 0.116541 0.509951 0.161204
'%f' 0.419433 0.274043 1.062142 0.500665
'%f\n' 0.459403 0.280520 1.068277 0.516433
'%+6.4f\n' 0.387856 0.264889 1.016386 0.478026
'%c' 0.032373 0.062918 0.468587 0.102139
'%c\n' 0.065148 0.068547 0.503620 0.118624
'%s (chaîne constante)' 0.034375 0.099740 0.452058 0.108760
'%s (chaîne constante)\n' 0.034234 0.100634 0.442354 0.108973
'%s' 0.036803 0.102868 0.447704 0.110269
'%s\n' 0.039003 0.100901 0.457537 0.112071
'%20s\n' 0.198806 0.130172 0.474586 0.289830
'%d blah blah: %s%f\n' 0.604971 0.418086 2.188021 0.812509
cpp11.thread.no_sync
'%d' 0.067131 0.108851 0.479392 0.146475
'%d\n' 0.100783 0.115027 0.503999 0.163044
'%f' 0.388417 0.273822 1.025720 0.483775
'%f\n' 0.419818 0.280391 1.030705 0.493496
'%+6.4f\n' 0.382446 0.264789 0.988382 0.467702
'%c' 0.026136 0.062212 0.461026 0.106458
'%c\n' 0.052622 0.067718 0.464902 0.113045
'%s (chaîne constante)' 0.030520 0.100459 0.443730 0.117889
'%s (chaîne constante)\n' 0.030301 0.100305 0.443197 0.117772
'%s' 0.032202 0.102627 0.448466 0.114538
'%s\n' 0.034557 0.100783 0.449142 0.117577
'%20s\n' 0.091444 0.130085 0.486534 0.188111
'%d blah blah: %s%f\n' 0.609628 0.417566 2.116151 0.818492

mercredi 15 mai 2013

Référence constante sur référence

Une petite note sur les références et le qualificatif const en commençant par un exemple :).

typedef int& reference;
int i = 0;
const reference r = i;
r = 3; //possible car r est en réalité un int&

Comme le montre l'exemple, r est modifiable alors que const est présent. En fait, ajouter const sur une référence ne fait rien. Pareil pour volatile cela dit.

J'aurais pu mettre `int & const r` mais comme const ne peut s'appliquer directement sur une référence, j'ai dû ruser ;).

Si on considère les références comme des pointeurs constants non nuls (car c'est ce qu'elles sont) et en transformant le premier exemple, on obtient:

typedef int * const pointeur_constant;
int i = 0;
const pointeur_constant r = &i;

Donc
decltype(r) = pointeur_constant const = int * const const = int * const = pointeur_constant

À ce moment on se rend compte que le type de r n'est autre que pointeur_constant. const n'a fait qu'ajouter une information déjà présente, il n'a donc aucune utilité.