30 octobre 2008

Optimiser une application PHP avec Xdebug.

Cet article tente de décrire la méthode utilisée pour optimiser le code de Pxxo à partir des données générées par Xdebug.

On considère ici que Xdebug est déjà installé.

Activer le profiler Xdebug

Xdebug propose en standard un profiler de code pour PHP. Celui permet de tracer et chronométrer tous les appels de méthodes et de fonctions php natives ou non. Pour activer cette fonctionnalité, il suffit de modifier la configuration de Xdebug. Pour cela, on doit modifier le fichier php.ini ou le fichier xdebug.ini.

Exemple sous Linux Debian avec le fichier xdebug.ini


zend_extension=/usr/lib/php5/20060613+lfs/xdebug.so

xdebug.profiler_append = 1
xdebug.profiler_enable  = 1
xdebug.profiler_output_dir = /data/thouveni/tmp
xdebug.profiler_output_name = cachegrind.out.%s

Écrire un script de test

Le profiler Xdebug génère énormément d'information. Vouloir analyser une application complète me semble inapproprié tant le volume de données produites risque d'être important (plusieurs Giga). Il est donc préférable d'isoler certaines fonctionnalités pour les tester unitairement.

On utilise Xdebug pour analyser une portion de code particulière plutôt qu'une application complète.

Il est pratique de créer un script dédié à l'exécution de la portion de code que l'on a isolée. Idéalement, ce script pourra également rejouer en boucle la partie à évaluer pour mieux faire ressortir les différences entre chaque appel.

Dans le cas de Pxxo, le script dédié est le suivant :


require_once 'Pxxo/Widget/Fake.php';

$p = array();
$p['ResourcePath'] = rtrim(dirname(__FILE__), DIRECTORY_SEPARATOR).'/rsc';   
$p['ResourceURL']  = rtrim(dirname($_SERVER['PHP_SELF']), DIRECTORY_SEPARATOR).'/rsc';
$p['Lang']         = 'fr';
$p['CacheMode']    = true;
$p['CachePath']    = sys_get_temp_dir();

for($z = 0; $z < 200; $z++) {
    $o = new Pxxo_Widget_Fake($p);
    $o->main();
    $o->get();
}

Lancer l'évaluation

Une fois le profiler activé dans la configuration de Xdebug, il suffit de relancer son serveur pour générer des fichiers de log.

Personnellement, j'active le profiler uniquement le temps d'exécuter le script de test. Cela évite de produire inutilement de la log et garantit que la log ne contiendra que les traces de l'appel à mon script.

Bien sûr, il vaut mieux activer le profiler sur un serveur de test et non sur un serveur de production. Encore une fois, trop de requête risque de produire trop de log.

Visualiser les résultats

Xdebug produit des logs au format cachegrind. Il existe à ma connaissance 2 outils libres pour analyser ce type de fichier, l'un sous Windows WinCacheGrind et l'autre sous Linux KCachegrind.

Pour avoir utiliser les deux, je conseille l'outil Linux. Car l'outil Windows n'accepte pas les gros fichiers de log et il n'implémente pas toutes les fonctionnalités proposées par KCachegrind, notamment la génération de carte graphique.

KCachegrind propose une interface en deux parties. La première affiche la liste des appels tracés :

http://www.touv.fr/IMG/png/kcachegring.pxxo.V2.1.png

La seconde propose une vue détaillée de chaque appel. La vue la plus simple à exploiter est celle présentée dans l'onglet "Callee Map". On peut rapidement et visuellement identifier quels sont les sous-appels qui prennent le plus de temps.

http://www.touv.fr/IMG/png/kcachegring.pxxo.V2.2.png

Analyser les résultats

Les graphiques produits par KCachegrind sont peut-être jolis mais il est quand même difficile de les analyser. Voici une façon parmi d'autres de procéder.

Le but est de chercher les appels les plus couteux en temps d'exécution afin de voir si on peut améliorer les choses. Pour cela, il y a plusieurs techniques :

Technique 1.

  • Trier par ordre décroissant la colonne "Self." dans la partie gauche de KCachegrind, de manière à voir tout de suite les appels qui prennent le plus de temps.
  • Repérer ceux qui sont appelés le plus souvent.
  • Repérer ceux qui ont la "Map Callee" avec le moins de couleurs ! Plus sérieusement, il s'agit d'identifier les appels dont le temps d'exécution propre est 4 à 5 fois plus importants que pour les sous-appels.

Technique 2.

Cette technique n'est qu'une variante de la technique précédente.

  • Trier par ordre décroissant la colonne "Called" dans la partie gauche de KCachegrind, de manière à voir tout de suite les appels qui sont le plus souvent appelés.
  • Repérer ceux qui ont le temps d'exécution propre les plus importants.
  • Repérer ceux qui ont la "Map Callee" avec le moins de couleurs !

Exemple avec Pxxo

Si on applique la technique n°1 sur le script de test de Pxxo, on peut remarquer la fonction addCacheID :

http://www.touv.fr/IMG/png/kcachegring.pxxo.V2.a.png

http://www.touv.fr/IMG/png/kcachegring.pxxo.V2.b.png

Cette méthode prend du temps, elle est beaucoup appelée et c'est majoritairement elle qui consomme le plus par rapport à ses sous-appels.

Il convient donc de regarder le code de cette méthode afin de voir :

  • si le code ne peut être amélioré
  • si cette méthode est appelée à bon escient

Dans le cas Pxxo, cette méthode n'est pas appelée à bon escient, Son but est de deviner le type des paramètres qu'on lui envoie afin d'en faire des chaines de caractères qu'elle stockera dans un tableau interne.

Clairement, si dans un programme, on manipule déjà une chaine de caractères, il semble inutile de vouloir deviner son type. Il vaut mieux créer une nouvelle méthode qui se chargera directement de stocker cette chaine dans le tableau interne.

Résultats

http://www.touv.fr/IMG/png/kcachegring.pxxo.V2.c.png

Avant Après
Self. Called Self. Called
addCacheID 5,99 1200 3,84 600
addRawCacheID 0 0 0.06 600
Total 5,99 1200 3,90 1200

Soit un gain de 2,09. Bien sûr, cela n'est pas suffisant, il faut réitérer la technique n°1. Et cette fois-ci, on remarque la méthode loadParamaters.

http://www.touv.fr/IMG/png/kcachegring.pxxo.V3.a.png

http://www.touv.fr/IMG/png/kcachegring.pxxo.V3.b.png

Comme la méthode précédente, elle consomme beaucoup, sans être relativement beaucoup appelée.

Contrairement au cas précédent, cette méthode est appelée à bon escient. C'est donc son code interne qui est mauvais. Il faut donc la réécrire différemment.

Résultats

http://www.touv.fr/IMG/png/kcachegring.pxxo.V4.a.png

http://www.touv.fr/IMG/png/kcachegring.pxxo.V4.b.png

Avant Après
9,30 2,41

Soit un gain de 6,89. Et on peut continuer comme cela autant de fois que l'on souhaite...

Conclusion

Xdebug est un bon outil pour ceux qui souhaitent améliorer finement leur code PHP. Par contre, si le but ultime de l'optimisation est l'amélioration des performances d'une application PHP alors avant d'utiliser Xdebug, il convient de regarder ailleurs. Car si Xdebug permet d'optimiser son code PHP, les gains obtenus sont dans la plupart des cas minimes et ils ne règleront probablement pas les gros problèmes de lenteur.

10 octobre 2008

Les performances des systèmes de cache en PHP en question.

Les sites WEB se doivent d'être rapide. Dans le cas contraire, le développeur va chercher à optimiser son application et il peut être tenté par des systèmes de cache. Mais quel système de cache choisir ? Sont-il vraiment efficaces ?

Lieu de stockage

Le principal critère de sélection d'un mécanisme de cache est son type de stockage. En voici 3 :

  • Système de fichiers
  • Mémoire partagée
  • Base de données

Pour chaque type, il existe plusieurs logiciels ou
techniques permettant de réaliser le stockage. Pour le système de fichiers, on trouve différents formats de fichier. Pour la mémoire partagée, on trouve : APC, Xdebug, Memcached. Et pour les bases de données, on trouve : Sqlite, Mysql, etc ...

Implémentation en PHP

Pour chaque méthode de stockage et pour obtenir un mécanisme de cache, il est nécessaire d'encapsuler les primitives PHP. On trouve de nombreuses API réalisant ce travail.

A l'heure actuelle, Le Zend Framework fournit une couche d'abstraction pour la majorité des méthodes de stockage citées ci-avant. C'est donc un bon moyen de tester la pertinence et la rapidité des différents types de stockage.

Il est difficile d'évaluer l'impact des différentes implémentations en PHP d'un moteur de cache. Il n'est donc pas question ici de les comparer.

Code à tester

Pour évaluer les performances des différentes méthodes de stockage, on va utiliser 2 séries.

Série 1 : 1 * 100000

La première série stocke en cache une seule information d'une taille importante. Par exemple avec le Zend_Framework, on aura le code suivant
:


$cache = Zend_Cache::factory('Core', $backend, $frontendOptions,
$backendOptions);

if (!($data = $cache->load($id))) {

   $data = '';
   for ($i = 0; $i < 100000; $i++) {
       $data .= '. ';
   }

   $cache->save($data);
   echo '<h1>SAVED</h1>';
}
else echo '<h1>CACHED</h1>';

echo '<div>'.$data.'</div>';

Série 2 : 1000 * 100

La deuxième série stocke en cache plusieurs informations de petite taille mais pour un volume total identique à la première série. Par exemple avec le Zend_Framework, on aura le code suivant :


$cache = Zend_Cache::factory('Core', 'APC', $frontendOptions,
$backendOptions);

$c = false;
$datas = '';
for ($j = 0; $j < 1000; $j++) {
   $id = 'ID'.$j;
   if (!($data = $cache->load($id))) {

       $data = '';
       for ($i = 0; $i < 100; $i++) {
           $data .= '. ';
       }

       $cache->save($data, $id);
       $c = true;
   }
   $datas .= $data;
}
if ($c)
   echo '<h1>SAVED</h1>';
else
   echo '<h1>CACHED</h1>';

echo '<div>'.$datas.'</div>';

Banc d'essais

Pour évaluer les performances, on utilisera en parallèle : ab, httperf
et siege. Ces 3 logiciels permettent de simuler une charge importante sur un serveur WEB tout en mesurant les temps de réponses.

Chacun de ces logiciels est lancé séquentiellement 4 fois, à chaque fois on calcul le nombre moyen de requêtes par seconde.

La comparaison portera sur :

  • Zend_Cache, backend : FILE
  • Zend_Cache, backend : APC
  • Zend_Cache, backend : SQLITE
  • Pxxo 5.4
  • Pxxo 5.4.1

Résultats

Les chiffres sont obtenus en faisant la médiane des 4 moyennes du nombre de requête par seconde. Le tout étant arrondi par défaut.

Série 1 : 1 * 10000 (sans APC)

ab httperf siege
php (sans cache) 20 11 22
pxxo (5.4.1) 208 111 216
pxxo (5.4) 265 136 127
zend_cache (apc) - - -
zend_cache (file) 153 84 156
zend_cache (sqlite) 129 69 134

Série 1 : 1 * 100000 (avec APC)

ab httperf siege
php (sans cache) 22 11 21
pxxo (5.4.1) 464 210 424
pxxo (5.4) 436 202 402
zend_cache (apc) 361 178 344
zend_cache (file) 295 144 278
zend_cache (sqlite) 136 109 208

Série 2 : 1000 * 100 (sans APC)

ab httperf siege
php (sans cache) 21 10 21
pxxo (5.4.1) 8 5 9
pxxo (5.4) 6 3 6
zend_cache (apc) - - -
zend_cache (file) 3 1 3
zend_cache (sqlite) 6 3 6

Série 2 : 1000 * 100 (avec APC)

ab httperf siege
php (sans cache) 22 11 21
pxxo (5.4.1) 45 22 44
pxxo (5.4) 8 4 8
zend_cache (apc) 29 15 29
zend_cache (file) 3 1 3
zend_cache (sqlite) 6 3 6

Conclusion

Comme d'habitude, tout va dépendre de votre contexte. Cependant, on
peut tirer quelques enseignements de ce ban d'essai.

  • Tout d'abord, il est efficace d'utiliser un système de mise en cache pour éviter un traitement long. Mais, plus on va utiliser le système de mise en cache, moins il devient intéressant de l'utiliser.
  • Ensuite, ce test confirme que c'est toujours mieux d'utiliser APC ou du moins de l'activer. Cependant, APC n'est pas toujours disponible et il convient de trouver une solution de rechange.
  • Si le besoin de mise en cache se limite à une seule information, vous pouvez utiliser Zend_Cache avec un stockage sous forme de fichiers (FILE). L'utilisation du backend APC vous apporte un peu plus mais ce n'est même pas obligatoire.
  • Plus vous souhaitez utiliser le stockage en cache, moins vous devez

utiliser le stockage par fichier et plus l'utilisation d'APC devient
obligatoire. Sans APC, SQLITE est une bonne solution de repli.

Le cas Pxxo

Pxxo utilise intensément la mise en cache. Depuis l'origine, le but était de pouvoir limiter le nombre de traitement d'un appel sur
l'autre et le tout de manière presque transparente pour le développeur.

Pxxo, jusqu'à la version 5.4, a toujours utilisé des mécanismes de cache tierces (Cache_Lite de PEAR au début puis Zend_Cache ensuite).
Maintenant, Pxxo utilise son propre mécanisme. Le but était d'améliorer les performances en mode non APC. Cette petite étude permet de confirmer que le but est atteint.

Code source du test

http://www.touv.fr/IMG/zip/cachesystem-2.zip