Profiling de dotclear

Le profiling est une technique courante pour trouver les bottleneck dans du code. Il est implémenté soit dans le compilateur (dans le cas d'un langage compile) soit dans l'interpréteur ou une extension.

Installer xdebug :

$ aptitude install php5-xdebug

Ajouter dans /etc/php5/conf.d/xdebug.ini :

zend_extension=/usr/lib/php5/20060613+lfs/xdebug.so
# append ?XDEBUG_PROFILE=1 to profile an url
xdebug.profiler_enable_trigger = on
xdebug.profiler_output_dir = /tmp/xdebug

Pour profiler une url, tapper l'url et lui ajouter ?XDEBUG_PROFILE=1 Lorsque la page est traitée, des fichiers cachegrind.out.xxx sont crées dans /tmp/xdebug.

Ensuite j'utilise kcachegrind (très pratique aussi pour analyser les résultats d'OProfile et Valgrind) pour analyser le profile.

Et la je vois 2 936 appels a netSocketIterator->current . Surprise ! Juste au-dessus, le coupable est enfin dévoilé : tplamis::amisWidget. Le widget pour récupérer des feeds ! Entre la connexion au serveur distant, la récupération du feed et le parsing du xml, ça a de quoi faire ramer le chargement de la page.

Donc zou' ! Suppression des widgets d'agrégation de flux qui n'étaient pas très utiles de toutes façons.

Deuxième phase, ajout de mécanisme de cache

Ici rien de bien complique non plus puisqu'il s'agit essentiellement d'installation de softs : xcache pour php, memcached et le plugin memcache pour dotclear.

xcache est implémenté un cache et un optimisateur des opcodes php.

$ aptitude install php5-xcache 

Ajuster les variables xcache.size et xcache.optimizer dans le fichier /etc/apache2/php5/xcache.ini :

xcache.size = 32M # ou plus selon vos besoins
xcache.optimizer = On

note : nécessite d'ajouter le repo lenny ou un repo de backports.

memcached est un serveur de cache distribue. Il écoute par défaut sur localhost:11211 :

$ aptitude install memcached

Et enfin télécharger le plugin memcache pour dotclear2 puis le décompresser dans le repertoire plugins/ de l'install dotclear sur le serveur.

Consommation mémoire par Apache2 et nginx/php5-fastcgi

En bonus un petit comparatif :

$ ps -o cmd,pid,rsz,vsz -C php5-cgi
CMD                           PID   RSZ    VSZ
/usr/bin/php5-cgi           26576  5172  50344
/usr/bin/php5-cgi           26577  5172  50344
/usr/bin/php5-cgi           26578 16892  56468
/usr/bin/php5-cgi           26579  2076  50344
/usr/bin/php5-cgi           26580  2076  50344
/usr/bin/php5-cgi           26581  2076  50344
/usr/bin/php5-cgi           26582  2076  50344
/usr/bin/php5-cgi           26583  2076  50344
/usr/bin/php5-cgi           26584  2076  50344
/usr/bin/php5-cgi           26585  2076  50344
/usr/bin/php5-cgi           26586  2076  50344
/usr/bin/php5-cgi           26587  2076  50344

(lancé avec /usr/local/bin/spawn-fcgi -f /usr/bin/php5-cgi -F 2 -s /var/run/nginx/fastcgi.sock -u www-data -g www-data)

Et nginx :

CMD                           PID   RSZ    VSZ
nginx: master process /usr/ 13828   440   2648
nginx: worker process       13829  1084   2808

Et pour apache2 :

CMD                           PID   RSZ    VSZ
/usr/sbin/apache2 -k start  24880  8464  54824
/usr/sbin/apache2 -k start  24885 15116  57520
/usr/sbin/apache2 -k start  24886 16116  58004
/usr/sbin/apache2 -k start  24887 13292  56576
/usr/sbin/apache2 -k start  24888 15308  57896
/usr/sbin/apache2 -k start  24889 13528  56620
/usr/sbin/apache2 -k start  25196 13780  56728
/usr/sbin/apache2 -k start  26564  4520  54956

note : rsz = mémoire résidente (RAM physique), vsz = mémoire virtuelle (allouée par l'application mais par forcément utilisée, et répartie entre la RAM et la swap).

Bien sur ça évolue en fonction des scripts qui sont appelés, mais ça donne un petit aperçu de la différence.

Par exemple, je viens de charger la page d'accueil du blog servie par nginx et j'ai maintenant :

CMD                           PID   RSZ    VSZ
/usr/bin/php5-cgi           13812  5172  50344
/usr/bin/php5-cgi           13814 17188  55820
/usr/bin/php5-cgi           13815  5172  50344
/usr/bin/php5-cgi           13817 13096  53376
/usr/bin/php5-cgi           13820  2076  50344
/usr/bin/php5-cgi           13831  2076  50344

Conclusion

Comme l'a dit Don Knuth, il ne faut pas optimiser prématurément (à part sur des parties évidentes ou des algorithmes bien connus pour une tache précise). Il faut surtout analyser le comportement de l'application et découvrir ses goulots d'étranglement. Comme on l'a vu ici c'est l'usage inapproprié (ou sans trop de réflexion préalable) qui ralentissait le chargement. En continuant la démarche de profiling et d'analyse il sera possible de découvrir les fonctions appelées trop souvent ou les requêtes mysql qui seraient trop coûteuses.

D'autre part, ça souligne qu'il n'est pas toujours - voire rarement mais il faudrait le démontrer ;) - optimal d'intégrer un peu tout dans un application web et de l'exécuter à la demande (ou à chaque chargement) certaines fonctions. Dans le cas d'un agrégateur il sûrement plus efficace de lancer une tache cron regulierement pour générer du html qui serait charge par un plugin.

[edit] Si vous passez par là, n'hésitez pas à me laisser un commentaire avec le temps de chargement de la page dans votre navigateur ;).