Pascal MARTIN (n+1).zéro http://blog.pascal-martin.fr/ Mots de Développement Web : PHP & JavaScript fr Fri, 24 May 2013 22:39:36 +0200 © Pascal MARTIN http://blogs.law.harvard.edu/tech/rss AstralBlog Bench PHP 5.2 vs PHP 5.3 http://blog.pascal-martin.fr/post/bench-php-5.2-vs-php-5.3-cpu?utm_source=rss2&utm_medium=feed&utm_campaign=bench-php-5.2-vs-php-5.3-cpu http://blog.pascal-martin.fr/post/bench-php-5.2-vs-php-5.3-cpu Mon, 20 Jul 2009 07:00:00 +0200 Pascal MARTIN benchmarkperformancephpphp-5.3 <p>Avec la sortie de PHP 5.3 et l’apparition de nouvelles fonctionnalités, une question est fréquemment posée : qu’est-ce que vaut PHP 5.3 au niveau des performances ? Est-ce que toutes ces nouveautés ne vont pas avoir d’impact négatif ?<sup>[<a href="#wiki-footnote-1">1</a>]</sup></p> <p>Voila plus d’un an que PHP 5.3 est annoncée comme plus rapide que PHP 5.2 <em>(Cf <a href="http://news.php.net/php.internals/36484">ce post sur internals@</a>, par exemple)</em> ; j’avais moi-même effectué <a href="/post/php-5.3-ameliorations-diverses#benchmark">quelques benchmarks synthétiques</a>, dont j’avais posté les résultats en novembre dernier.</p> <p>Mais ces tests, qui montraient un gain de performance de 10 à 20% pour PHP 5.3 par rapport à PHP 5.2, n’avaient pas été joués avec la version finale de PHP 5.3.0, d’une part, et ne testaient que des fonctions internes de PHP <em>— pas une véritable application</em>.</p> <p>Voici donc quelques résultats obtenus avec des « vraies » applications, comportant de nombreux fichiers, des appels à une Base de Données, … <br>… En somme, des applications du genre de celles que nous sommes susceptibles d’utiliser tous les jours sur nos serveurs !</p> <p><strong>Sommaire de cet article :</strong></p> <ul> <li> <a href="/post/bench-php-5.2-vs-php-5.3-cpu#methodologie">Méthodologie</a> <ul> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#logiciels-testes">Logiciels testés</a></li> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#versions-php">Versions de PHP testées</a></li> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#deux-series-tests">Deux séries de tests</a></li> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#methodologie-2">Méthodologie</a></li> </ul> </li> <li> <a href="/post/bench-php-5.2-vs-php-5.3-cpu#resultats">Résultats obtenus sans APC</a> <ul> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#wordpress">Wordpress</a></li> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#drupal">Drupal</a></li> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#quickstart-zf">Quickstart Zend Framework</a></li> </ul> </li> <li> <a href="/post/bench-php-5.2-vs-php-5.3-cpu#resultats-apc">Résultats obtenus avec APC</a> <ul> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#wordpress-apc">Wordpress</a></li> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#drupal-apc">Drupal</a></li> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#quickstart-zf-apc">Quickstart Zend Framework</a></li> </ul> </li> <li> <a href="/post/bench-php-5.2-vs-php-5.3-cpu#conclusion">Conclusion</a> <ul> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#gain-performances">PHP 5.3 : gain de performances !</a></li> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#quel-interet">Quel intérêt ?</a></li> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#disclaimer">Disclaimer</a></li> <li><a href="/post/bench-php-5.2-vs-php-5.3-cpu#aller-plus-loin">Aller plus loin ? D’autres tests ?</a></li> </ul> </li> </ul> <p><br></p> <h2>Méthodologie <a name="methodologie"></a> </h2> <p>Si vous voulez <a href="/post/bench-php-5.2-vs-php-5.3-cpu#resultats">aller directement aux résultats</a>…</p> <h3>Logiciels testés <a name="logiciels-testes"></a> </h3> <p>J’ai effectué mes tests avec trois logiciels, en installation « de base », sans ajouter de plugin ni quoi que ce soit :</p> <ul> <li>Wordpress 2.8.1 : <a href="http://wordpress.org/">http://wordpress.org/</a> <ul> <li>La page testée correspond à la page d’accueil du blog, en front office</li> </ul> </li> <li>Drupal 6.13 : <a href="http://drupal.org/">http://drupal.org/</a> <ul> <li>La page testée est la page d’accueil du site, avec deux articles créés précédemment via le Back Office</li> </ul> </li> <li>Le « Quick Start » de Zend Framework : <a href="http://framework.zend.com/docs/quickstart">http://framework.zend.com/docs/quickstart</a> <ul> <li>La page testée est celle de « guestbook »</li> </ul> </li> </ul> <p><br></p> <h3>Versions de PHP testées <a name="versions-php"></a> </h3> <p>Pour ces tests, j’ai utilisé deux versions de PHP, toutes deux compilées par moi-même, avec les mêmes options de configuration. <br>Ces deux versions de PHP sont chargées en tant que module Apache, via deux instances différentes d’Apache<sup>[<a href="#wiki-footnote-2">2</a>]</sup> :</p> <ul> <li>PHP 5.2.9, avec Apache écoutant sur les ports 529**</li> <li>Et PHP 5.3.0, avec Apache écoutant sur les ports 530**</li> </ul> <p>Les options de compilation et de configuration ne sont certainement pas optimales : à peu de chose près, j’ai conservé toutes les valeurs par défaut ; mais, considérant que je n’ai tunné ni la première installation, ni la seconde, la comparaison devrait ne pas être « trop faussée ».</p> <p>Les sorties de <code>phpinfo()</code> sont disponibles ici, si vous voulez en savoir plus :</p> <ul> <li><a href="/public/php-5.3/bench-php53/phpinfo-5.2.9.html">phpinfo PHP 5.2.9</a></li> <li><a href="/public/php-5.3/bench-php53/phpinfo-5.3.0.html">phpinfo PHP 5.3.0</a></li> </ul> <p><em>(Ces sorties de <code>phpinfo()</code> ont été enregistrées alors que j’avais activé l’extension APC)</em></p> <p><br></p> <h3>Deux séries de tests <a name="deux-series-tests"></a> </h3> <p>Les trois applications ont été testées une première fois sans que l’extension <a href="http://www.php.net/apc">APC</a> ne soit activée.</p> <p>Les mêmes tests ont ensuite été rejoués avec l’extension APC activée.</p> <p><br></p> <h3>Méthodologie <a name="methodologie-2"></a> </h3> <p>Les trois applications ont été testées en utilisant l’outil <a href="http://httpd.apache.org/docs/2.2/programs/ab.html">ab</a>, avec les options suivantes, qui me semblent raisonnables pour ma machine de tests :</p> <ul> <li>concurrence = 10</li> <li>nombre d’occurrences = 1000</li> </ul> <p>Le test est lancé une première fois sur l’instance en PHP 5.2.9, puis sur l’instance en PHP 5.3.0. <br>Ceci est répété 10 fois de suite <em>(on a donc un test sur PHP 5.2.9, puis un test sur 5.3.0, puis un test sur 5.2.9, puis sur 5.3.0, etc…)</em></p> <p>J’ai ensuite calculé la moyenne des 10 séries de test pour PHP 5.2.9 et pour PHP 5.3.0 ; c’est cette moyenne qui me sert de résultat pour comparer les deux versions.</p> <p>Et voici quelques éléments de la machine de tests :</p> <ul> <li>CPU Core 2 Quad 9550 ; les quatre cœurs étaient utilisés à 100% pendant l’exécution des tests</li> <li>6 GB de RAM ; Il restait en permanence de la RAM libre pendant l’exécution des tests</li> <li>Les binaires d’Apache, PHP, et les scripts testés sont sur un disque dur 10k rpm</li> <li>Et l’OS est un Linux Ubuntu 64 bits.</li> </ul> <p><br></p> <h2>Résultats obtenus sans APC <a name="resultats"></a> </h2> <p>Pour commencer, voici les résultats obtenus pour chacun des trois logiciels testés, sans APC :</p> <h3>Wordpress <a name="wordpress"></a> </h3> <ul> <li>PHP 5.2.9 : 33.26 requêtes / seconde</li> <li>PHP 5.3.0 : 41.54 requêtes / seconde</li> <li>Différence : 124.91 %</li> </ul> <p><a href="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-wordpress-1.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="php-5.2-vs-php-5.3-wordpress-1.png" title="php-5.2-vs-php-5.3-wordpress-1.png, juil. 2009" class="lazy" data-original="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-wordpress-1.png" width="450px" height="384px"><noscript><img src="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-wordpress-1.png" style="" alt="php-5.2-vs-php-5.3-wordpress-1.png" width="450px" height="384px"></noscript></a></p> <p><em>Pour ce graphique, comme pour tous les autres présentés dans cet article, plus la barre correspondant à une version de PHP est haute, plus cette version de PHP est capable de répondre à un nombre élevé de requêtes par seconde — mieux c’est niveau performances, donc.</em></p> <h3>Drupal <a name="drupal"></a> </h3> <ul> <li>PHP 5.2.9 : 51.33 requêtes / seconde</li> <li>PHP 5.3.0 : 63.58 requêtes / seconde</li> <li>Différence : 123.87 %</li> </ul> <p><a href="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-drupal-1.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="php-5.2-vs-php-5.3-drupal-1.png" title="php-5.2-vs-php-5.3-drupal-1.png, juil. 2009" class="lazy" data-original="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-drupal-1.png" width="452px" height="384px"><noscript><img src="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-drupal-1.png" style="" alt="php-5.2-vs-php-5.3-drupal-1.png" width="452px" height="384px"></noscript></a></p> <h3>Quickstart Zend Framework <a name="quickstart-zf"></a> </h3> <ul> <li>PHP 5.2.9 : 77.28 requêtes / seconde</li> <li>PHP 5.3.0 : 97.79 requêtes / seconde</li> <li>Différence : 126.54 %</li> </ul> <p><a href="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-zf-quickstart-1.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="php-5.2-vs-php-5.3-zf-quickstart-1.png" title="php-5.2-vs-php-5.3-zf-quickstart-1.png, juil. 2009" class="lazy" data-original="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-zf-quickstart-1.png" width="450px" height="385px"><noscript><img src="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-zf-quickstart-1.png" style="" alt="php-5.2-vs-php-5.3-zf-quickstart-1.png" width="450px" height="385px"></noscript></a></p> <p><br></p> <h2>Résultats obtenus avec APC <a name="resultats-apc"></a> </h2> <p>Et maintenant, les résultats des mêmes tests, en ayant activé l’extension APC (aucune option de configuration n’a été définie dans le fichier <code>php.ini</code> ; toutes les valeurs de configuration sont donc celles par défaut <em>— même si ce n’est pas optimal</em>).</p> <h3>Wordpress <a name="wordpress-apc"></a> </h3> <ul> <li>PHP 5.2.9 : 128.66 requêtes / seconde</li> <li>PHP 5.3.0 : 139.38 requêtes / seconde</li> <li>Différence : 108.33 %</li> </ul> <p><a href="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-wordpress-apc-1.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="php-5.2-vs-php-5.3-wordpress-apc-1.png" title="php-5.2-vs-php-5.3-wordpress-apc-1.png, juil. 2009" class="lazy" data-original="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-wordpress-apc-1.png" width="450px" height="384px"><noscript><img src="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-wordpress-apc-1.png" style="" alt="php-5.2-vs-php-5.3-wordpress-apc-1.png" width="450px" height="384px"></noscript></a></p> <h3>Drupal <a name="drupal-apc"></a> </h3> <ul> <li>PHP 5.2.9 : 207.79 requêtes / seconde</li> <li>PHP 5.3.0 : 212.84 requêtes / seconde</li> <li>Différence : 102.43 %</li> </ul> <p><a href="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-drupal-apc-1.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="php-5.2-vs-php-5.3-drupal-apc-1.png" title="php-5.2-vs-php-5.3-drupal-apc-1.png, juil. 2009" class="lazy" data-original="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-drupal-apc-1.png" width="450px" height="384px"><noscript><img src="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-drupal-apc-1.png" style="" alt="php-5.2-vs-php-5.3-drupal-apc-1.png" width="450px" height="384px"></noscript></a></p> <h3>Quickstart Zend Framework <a name="quickstart-zf-apc"></a> </h3> <ul> <li>PHP 5.2.9 : 297.87 requêtes / seconde</li> <li>PHP 5.3.0 : 290.91 requêtes / seconde</li> <li>Différence : 97.66 %</li> </ul> <p><a href="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-zf-quickstart-apc-1.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="php-5.2-vs-php-5.3-zf-quickstart-apc-1.png" title="php-5.2-vs-php-5.3-zf-quickstart-apc-1.png, juil. 2009" class="lazy" data-original="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-zf-quickstart-apc-1.png" width="450px" height="385px"><noscript><img src="/public/php-5.3/bench-php53/php-5.2-vs-php-5.3-zf-quickstart-apc-1.png" style="" alt="php-5.2-vs-php-5.3-zf-quickstart-apc-1.png" width="450px" height="385px"></noscript></a></p> <p><br></p> <h2>Conclusion <a name="conclusion"></a> </h2> <p>Pour conclure, voici quelques mots — sans trop réfléchir non plus : c’est aussi à vous d’interprêter ces résultats et de voir en quoi est-ce qu’ils peuvent être intéressants dans votre situation !</p> <h3>PHP 5.3 : gain de performances ! <a name="gain-performances"></a> </h3> <p>Comme conclusion, je ferai volontairement simple : sur ces quelques tests d’applications « réelles », sur une installation PHP « standard », PHP 5.3.0 s’en sort nettement mieux que PHP 5.2.x : environ 25 % de requêtes par seconde supplémentaires !</p> <p>Une fois l’extension APC activée, le gain est nettement moins visible : seulement quelques pourcents, voire même une légère perte dans un cas :-( <br>Cela dit, on voit encore une fois l’intérêt d’activer APC, à partir du moment où vous êtes libre d’installer des extensions sur votre hébergement : sur ces tests, un très rapide calcul montre que vous répondez à environ trois fois plus de requêtes par seconde avec APC que sans !</p> <p>A quoi est dû ce gain ? Je n’entrerai pas dans les détails<sup>[<a href="#wiki-footnote-3">3</a>]</sup>, mais il y a fort à parier que les quelques optimisations citées ici n’y sont pas pour rien :</p> <ul> <li>Déplacement des constantes vers des zones mémoire en lecture-seule</li> <li>Amélioration du gestionnaire d’exceptions : il est maintenant plus simple, et tient sur moins de code.</li> <li>Suppression d’appels système à « fopen » pour les inclusions de fichiers via <code>require_once</code> et <code>include_once</code>, résultant en une amélioration de la rapidité de ces deux directives.</li> <li>Refactoring de la pile d’appels</li> <li>Utilisation de gcc4 pour la compilation.</li> </ul> <p>Pour plus d’informations, n’hésitez pas à consulter le <a href="http://php.net/ChangeLog-5.php">changelog de PHP 5.3.0</a>.</p> <p><br></p> <h3>Quel intérêt ? <a name="quel-interet"></a> </h3> <p>J’en entends déjà certains se demander Mais quel intérêt pour mon application ? Et quid du coût probable de migration de PHP 5.2 vers 5.3 ?</p> <p>Et bien, pour votre application toute seule, l’intérêt, c’est vous le mieux placé pour le déterminer ; mais pensez à ceci : 20% de gain CPU, si vous avez 10 serveurs faisant tourner du PHP, ça peut signifier que deux de ces serveurs pourraient être utilisés pour autre chose… A vous de voir l’économie que cela peut signifier<sup>[<a href="#wiki-footnote-4">4</a>]</sup> ;-)</p> <p>Après, oui, rendre votre application compatible PHP 5.3, et la re-tester, peut prendre du temps, et représenter un coût non négligeable — mais pour les futures applications que vous allez développer, qu’en est-il ? Pourquoi ne pas partir sur PHP 5.3 dès la phase de développement ?</p> <p><br></p> <h3>Disclaimer <a name="disclaimer"></a> </h3> <p>A noter tout de même : de manière générale, un serveur faisant tourner PHP aura aussi tendance à servir des fichiers statiques (JS, CSS, images, …), ce qui peut avoir un impact sur sa charge, indépendamment de PHP — et les tests présentés ici ne prennaient en compte que le chargement de la page PHP.</p> <p>A noter aussi : les tests réalisés ici ne testaient qu’une seule page de chaque application, et non chaque application dans sa globalité. Avant de vous lancer dans une <em>— possiblement coûteuse —</em> migration compléte d’application, il peut être intéressant de jouer des tests un peu plus approfondis !</p> <p><br></p> <h3>Aller plus loin ? D’autres tests ? <a name="aller-plus-loin"></a> </h3> <p><strong>Si vous effectuez d’autres benchmarks de ce type, ou plus poussés, je suis extrêmement intéressé ! N’hésitez pas à laisser un commentaire ;-)</strong></p> <p>En attendant, vous pouvez jeter un coup d’oeil sur ces deux benchmarks d’eZPublish, effectués il y a quelques semaines :</p> <ul> <li> <a href="http://ez.no/developer/forum/developer/performance_php_5_2_vs_php_5_3_huge_gain">Performance PHP 5.2 vs PHP 5.3 - huge gain</a> sous Lindows,</li> <li>et <a href="http://serwatka.net/blog/ez_publish_performance_with_php_5_3_0">eZ Publish performance with PHP 5.3.0</a> sous Linux</li> </ul> <p>Les gains de performance constatés sont du même type que ceux que j’obtiens dans cet article ; et ce, que ce soit sous Linux ou sous Windows, ce qui est une excellente nouvelle !</p> <p><br> Au passage, vous trouverez joint à cet article le fichier .ods que j’ai contruit, regroupant tous les résultats obtenus (et pas seulement les moyennes présentées ici).</p> <p><br></p> <div class="footnotes"> <h4>Notes</h4> <p>[<a href="#rev-wiki-footnote-1">1</a>] D’aucun diraient que ça fait deux questions, et pas une ; mais elles sont liées, donc bon…</p> <p>[<a href="#rev-wiki-footnote-2">2</a>] Il faudrait, un jour, que je rédige un article expliquant comment installer plusieurs versions de PHP sur une seule machine, d’ailleurs… ça fait plus d’un an que j’ai les notes nécessaires… il faut “juste” que je trouve le temps de rédiger…</p> <p>[<a href="#rev-wiki-footnote-3">3</a>] Détails que je ne connais pas en détails, d’ailleurs ^^</p> <p>[<a href="#rev-wiki-footnote-4">4</a>] OK, si vous en arrivez à calculer quelles économies vous pourriez réaliser de cette manière, changer de version de PHP n’est pas la seule chose à laquelle vous penserez : passer à un autre serveur (nginx, par exemple), ou installer un reverse proxy (varnish, par exemple) peuvent être deux bonnes idées)</p> </div> http://blog.pascal-martin.fr/post/bench-php-5.2-vs-php-5.3-cpu#comment-new-form http://blog.pascal-martin.fr/post/bench-php-5.2-vs-php-5.3-cpu#comment-new-form Google Gears : accélérer le chargement d'une application en cachant les fichiers statiques http://blog.pascal-martin.fr/post/Google-Gears-accelerer-chargement-application-cache-fichiers-statiques?utm_source=rss2&utm_medium=feed&utm_campaign=Google-Gears-accelerer-chargement-application-cache-fichiers-statiques http://blog.pascal-martin.fr/post/Google-Gears-accelerer-chargement-application-cache-fichiers-statiques Thu, 21 Aug 2008 08:00:00 +0200 Pascal MARTIN Javascriptgoogle-gearsperformance <p>Vous souhaitez proposer aux utilisateurs de votre application Web de continuer à travailler avec celle-ci même lorsqu’ils ne sont pas connectés à Internet ? <br>C’est possible : prenez par exemple <a href="http://docs.google.com/">Google Docs</a>, qui vous permet de modifier vos documents<sup>[<a href="#wiki-footnote-1">1</a>]</sup> sans être connecté !</p> <h2>Travailler hors ligne ?</h2> <p>Avant de commencer, alors que nous avons de plus en plus tendance à avoir accès à Internet où que nous allions, pourquoi se soucier de développer une application Web qui soit accessible hors-ligne ?</p> <h3>Introduction : accès Internet, ou isolement ?</h3> <p>Et bien… justement parce que nous avons de plus en plus accès à Internet : nous avons tellement l’habitude d’être connectés que nous utilisons de plus en plus de services Web quotidiennement, parfois même à la place des outils “déconnectés” que nous employions auparavant… Mais que se passe-t-il si nous sommes dans une situation où nous n’avons pas de connexion Internet (dans le train ? En vacances chez nos grand-parents qui ne connaissent pas encore Internet ? Au fond de la campagne sans connectivité 3G ? En réunion sans accès wifi ?) ?</p> <p>Cela implique-t-il que nous n’ayons pas accès auxquels nous nous sommes habitués ? Cela signifie-t-il que nous n’avons plus accès à nos données ? <br>Cette situation est-elle acceptable pour vous ? Dans certains cas, pas pour moi !</p> <p><br></p> <h3>Google Gears</h3> <p>Pour répondre au besoin qu’est la capacité pour une application de vous permettre de continuer à travailler hors-ligne, Google a développé <a href="http://fr.wikipedia.org/wiki/Gears">Google Gears</a>.</p> <p>Je cite WikiPedia :</p> <p>Gears est un prototype logiciel proposé par Google pour permettre l’accès hors-ligne à des services qui fonctionnent normalement en ligne. Il installe un moteur de base de donnée basé sur SQLite sur le système client pour cacher les données localement. Les pages pour lesquelles Google-Gears est activé utilisent les données en provenance de ce cache local plutôt que celles provenant du service en ligne. Si la connexion réseau n’est pas disponible, la synchronisation est ajournée jusqu’à ce que la connexion revienne. Gears permet donc à des applications web de fonctionner sans accès permanent au réseau.</p> <p>Gears fonctionne sous la majorité des navigateurs Web que vos utilisateurs sont susceptibles d’utiliser<sup>[<a href="#wiki-footnote-2">2</a>]</sup> :</p> <ul> <li>Firefox &gt;= 1.5 (Windows, MacOS X, Linux)</li> <li>Internet Explorer &gt;= 6 (Windows)</li> </ul> <p>Malheureusement, même si le support de Safari est prévu, il n’est pas encore disponible. <br><em>A vous de voir l’impact pour vous utilisateurs…</em></p> <p><br><strong>MAJ le 26/08/2008 :</strong> Une première version bêta de Gears est sortie pour Safari !</p> <p>Je cite, tout de même :</p> <p>This is BETA, it is not an official release, it might break your browser. Chances are it will break your browser. Please proceed with caution.</p> <p>Pour plus d’informations, allez voir :</p> <ul> <li><a href="http://googlesystem.blogspot.com/2008/08/gears-for-safari.html">http://googlesystem.blogspot.com/2008/08/gears-for-safari.html</a></li> <li>et pour l’annonce officielle sur le Google-group de Gears : <a href="http://groups.google.com/group/gears-users/browse_thread/thread/36537d4f47c5495c">http://groups.google.com/group/gears-users/browse_thread/thread/36537d4f47c5495c</a> </li> </ul> <p><br></p> <p><br></p> <h3>Une série d’articles sur Google Gears</h3> <p>Cet article est le premier d’une série - de trois, probablement, si je m’en tiens à mon plan - visant au développement d’une mini-application capable de vous permettre de travailler hors-ligne.</p> <p>Le plan prévu est le suivant :</p> <ul> <li>Premier article (celui-ci) : installation et activation de Google Gears, concepts de base, et utilisation pour stocker en cache, sur le poste utilisateur, les fichiers statiques utilisés par l’application.</li> <li>Second article : <a href="/post/Google-Gears-version-off-line-application">travail hors-ligne, sans le moindre appel au serveur</a>, une fois l’application en mode “déconnecté”.</li> <li>Troisième article <em>(octobre 2008 ?)</em> : synchronisation entre les données “en-ligne”, et le travail effectué “hors-ligne”, lors du retour en mode connecté.</li> </ul> <p><br></p> <h2>Application utilisée pour cet article</h2> <p>Pour cet article, nous développerons une application des plus simple : une application de prise de notes.</p> <h3>Une application simple</h3> <p>L’idée est la suivante :</p> <ul> <li>une note est composée d’un nom d’auteur, d’une date, et d’un texte</li> <li>un formulaire permet de saisir une note</li> <li>les trois champs sont obligatoires</li> <li>la liste des notes saisies est affichée au-dessus du formulaire de saisie.</li> </ul> <p>Au niveau de l’interface, on obtient quelque chose de ce genre : <a title="adding-a-note-1.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="adding-a-note-1.png" class="lazy" data-original="/public/google-gears/faster-loading/.adding-a-note-1_m.png" width="448px" height="278px"><noscript><img src="/public/google-gears/faster-loading/.adding-a-note-1_m.png" style="" alt="adding-a-note-1.png" width="448px" height="278px"></noscript></a></p> <p>Rien de très compliqué, donc ^^ <br><em>(Les sources de l’application sont disponibles en téléchargement ; Cf la pièce jointe à ce billet, tout en bas de celui-ci)</em></p> <p>Cette application est développée en PHP + SQLite, avec un brin de Javascript, CSS, et XHTML pour la couche présentation. <br>Pour faire simple, pas de MVC, pas d’orientation objet, juste un seul fichier .php pour le tout ; à vous de faire plus complet / plus organisé lorsque vous travaillerez sur une application réelle, et non un script d’exemple !</p> <p><br></p> <h3>Des temps de chargement non satisfaisant ?</h3> <p>Au fur et à mesure que vous ajoutez des fonctionnalités à vos pages, vous ajouterez - probablement - des fichiers que le navigateur client devra télécharger : feuilles de styles, fichiers Javascript, images, … Tout ceci finit par augmenter les durées de chargement des pages avec lesquelles vos visiteurs travaillent.</p> <p>Pour la page toute simple que nous avons présenté plus haut, voici la liste des fichiers chargés depuis l’en-tête de la page :</p> <pre><code>&lt;link href="css/style.css" media="screen" rel="stylesheet" type="text/css" /&gt; &lt;script type="text/javascript" src="js/lib/prototype.js"&gt;&lt;/script&gt; &lt;script type="text/javascript" src="js/lib/scriptaculous/builder.js"&gt;&lt;/script&gt; &lt;script type="text/javascript" src="js/lib/scriptaculous/effects.js"&gt;&lt;/script&gt; &lt;script type="text/javascript" src="js/lib/scriptaculous/dragdrop.js"&gt;&lt;/script&gt; &lt;script type="text/javascript" src="js/lib/scriptaculous/controls.js"&gt;&lt;/script&gt; &lt;script type="text/javascript" src="js/application.js"&gt;&lt;/script&gt;</code></pre> <p>Ceci qui donne le graphe de chargement de page suivant :</p> <p><a title="firebug-network-without-gears.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="firebug-network-without-gears.png" class="lazy" data-original="/public/google-gears/faster-loading/.firebug-network-without-gears_m.png" width="448px" height="178px"><noscript><img src="/public/google-gears/faster-loading/.firebug-network-without-gears_m.png" style="" alt="firebug-network-without-gears.png" width="448px" height="178px"></noscript></a> <em>(Modulo un ou deux fichiers : cette capture correspond à un graphe de chargement de l’application complète)</em></p> <p>Déjà pour cette page simple, qui n’inclut qu’une seule feuille de style et seulement quelques fichiers JS, le chargement de ces fichiers externes - et statiques - prend plus de la moitié du temps de chargement ! Plus de la moitié du temps que l’utilisateur passe à attendre que la page s’affiche correspond à des fichiers qui sont quasiment toujours les mêmes : ils ne seront modifiés que lors de mises à jour de l’application, et il est à parier que celle-ci sera utilisé plus souvent qu’elle ne sera MAJ<sup>[<a href="#wiki-footnote-3">3</a>]</sup> !</p> <p>Gears va permettre de stocker ces fichiers directement sur les postes de vos utilisateurs, qui n’auront donc pas à les re-télécharger à chaque chargement de page. <br>Bien entendu, cela accélèrera le chargement de notre liste de notes… Mais, surtout, c’est un <strong>premier pas vers une version utilisable hors-ligne</strong> de notre site !</p> <p>Quelques remarques :</p> <ul> <li>Pour montrer l’intérêt de ce dont je parle dans cet article, j’ai volontairement inclus plus de fichiers JS que nécessaire pour cette application (j’ai inclus une partie de <a href="http://script.aculo.us/">script.aculo.us</a>) : pour une application un tant soit peu plus complexe, vous en inclurez probablement encore d’autres.</li> <li>D’un autre côté - et ça vaut mieux considérant mes talents de graphiste - je n’ai pour ainsi dire pas inclus d’image ; disons que ça compense… Plus ou moins.</li> <li>Bien entendu, dans un cas réel, votre serveur serait certainement configuré pour indiquer au navigateur qu’il doit conserver les fichiers css/js/images en cache, et ne pas les re-télécharger à chaque chargement de page ! Mais cela ne nous aiderait pas vraiment à rendre notre application accessible hors-ligne, n’est-ce pas ?</li> </ul> <p><br></p> <h2>Premiers pas avec Google gears</h2> <p>Avant de réellement pouvoir travailler avec gears, il vous faut inclure un fichier Javascript fourni par Google, et, bien évidemment, vous assurer que l’utilisateur de votre site a installé le plugin attendu.</p> <h3>Inclusion du fichier gears_init.js</h3> <p>Pour inclure des fonctionnalités liées à Gears sur votre site, vous devez inclure sur vos pages le fichier “<code>gears_init.js</code>” fourni par Google : <a href="http://code.google.com/apis/gears/tools.html#gears_init">gears_init.js</a>.</p> <p>L’idée est la suivante :</p> <ul> <li>vous téléchargez le fichier fourni par google,</li> <li>vous le stockez sur votre propre serveur, avec vos autres fichiers Javascript,</li> <li>et vous l’incluez sur vos pages, via une balise <code>&lt;script&gt;</code> tout ce qu’il y a de plus standard :</li> </ul> <pre><code>&lt;script type="text/javascript" src="js/lib/gears_init.js"&gt;&lt;/script&gt;</code></pre> <p>Ceci vous permettra d’accéder à l’outil Google gears depuis vos scripts.</p> <p><br></p> <h3>Installation de Gears<a name="installation"></a> </h3> <p>Une fois le fichier <code>gears_init.js</code> inclut, vous pouvez commencer à travailler avec l’objet <code>google.gears</code>. <br>Typiquement, le premier pas sera de détecter si l’utilisateur de votre site a installé l’extension / le plugin<sup>[<a href="#wiki-footnote-4">4</a>]</sup> google gears, et, si ce n’est pas le cas, de lui proposer de l’installer.</p> <p>Pour détecter si le plugin gears n’est pas installé, il suffit de deux tests Javascript :</p> <pre><code>if (!window.google || !google.gears) { // Extension google gears non installé // =&gt; Afficher un lien menant à son installation }</code></pre> <p>Le lien proposant d’installer Google Gears doit pointer sur le site de Google ; plus précisément, sur la page permettant… l’installation du-dit plugin. <br>Il doit être construit de la manière suivante : <br><code>http://gears.google.com/?action=install&amp;name=<strong>&lt;NOM_APPLICATION&gt;</strong>&amp;message=<strong>&lt;MESSAGE&gt;</strong>&amp;return=<strong>&lt;URL_DE_RETOUR&gt;</strong>&amp;icon_src=<strong>&lt;URL_ICONE&gt;</strong></code></p> <p>Bien entendu, à vous d’assigner à chaque variable les valeurs qui correspondent à votre site / votre application :</p> <ul> <li> <code>action</code> = <code>install</code> ou <code>update</code>. De manière générale, si google gears n’est pas installé, vous proposerez à votre visiteur de l’installer.</li> <li> <code>name</code> = le nom de votre application ; au maximum, 150 caractères.</li> <li> <code>message</code> = un message à afficher sur la page d’installation de gears ; au maximum, 150 caractères.</li> <li> <code>return</code> : l’URL de la page de votre site vers laquelle l’utilisateur reviendra après installation du plugin.</li> <li> <code>icon_src</code> : l’URL d’une icône à afficher sur la page d’installation de gears ; sa taille doit être de 48x48 pixels.</li> </ul> <p>Un point intéressant à noter : pensez à échapper les caractères spéciaux de vos messages, url, … <br>Mais cet échappement doit être conforme à la RFC 1738 ; cela signifie que le caractère espace ne doit pas être remplacé par un “<code>+</code>“ ! <br>Pour les développeurs PHP, notamment, cela signifie qu’il faut utiliser <a href="http://fr2.php.net/rawurlencode">rawurlencode</a>, et non <a href="http://fr.php.net/urlencode">urlencode</a> !</p> <p>Ci-dessous, quelques captures d’écran de l’installation de Google gears, sous Firefox 3.0, linux :</p> <p>Le lien sur notre application proposant d’installer Gears, visible seulement lorsque nous avons détecté que l’extension n’était pas déjà installée : <a title="installer-google-gears-1.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="installer-google-gears-1.png" class="lazy" data-original="/public/google-gears/faster-loading/.installer-google-gears-1_m.png" width="448px" height="273px"><noscript><img src="/public/google-gears/faster-loading/.installer-google-gears-1_m.png" style="" alt="installer-google-gears-1.png" width="448px" height="273px"></noscript></a></p> <p>La page d’installation du plugin gears sur le site de Google : notez la présence des nom, message, et icône, que nous avions spécifiés dans le lien : <a title="installer-google-gears-2.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="installer-google-gears-2.png" class="lazy" data-original="/public/google-gears/faster-loading/.installer-google-gears-2_m.png" width="448px" height="207px"><noscript><img src="/public/google-gears/faster-loading/.installer-google-gears-2_m.png" style="" alt="installer-google-gears-2.png" width="448px" height="207px"></noscript></a></p> <p>Une fois que l’utilisateur a cliqué sur le bouton “Install gears”, il doit accepter les conditions d’utilisation : <a title="installer-google-gears-3.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="installer-google-gears-3.png" class="lazy" data-original="/public/google-gears/faster-loading/.installer-google-gears-3_m.png" width="448px" height="241px"><noscript><img src="/public/google-gears/faster-loading/.installer-google-gears-3_m.png" style="" alt="installer-google-gears-3.png" width="448px" height="241px"></noscript></a></p> <p>Puis l’installation, sous Firefox, du moins, se fait comme pour n’importe quelle extension : <a title="installer-google-gears-4.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="installer-google-gears-4.png" class="lazy" data-original="/public/google-gears/faster-loading/.installer-google-gears-4_m.png" width="448px" height="224px"><noscript><img src="/public/google-gears/faster-loading/.installer-google-gears-4_m.png" style="" alt="installer-google-gears-4.png" width="448px" height="224px"></noscript></a></p> <p>Avec le redémarrage obligatoire de Firefox en fin de processus d’installation : <a title="installer-google-gears-5.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="installer-google-gears-5.png" class="lazy" data-original="/public/google-gears/faster-loading/.installer-google-gears-5_m.png" width="448px" height="261px"><noscript><img src="/public/google-gears/faster-loading/.installer-google-gears-5_m.png" style="" alt="installer-google-gears-5.png" width="448px" height="261px"></noscript></a></p> <p>Maintenant, lors de la prochaine visite de l’utilisateur (lorsqu’il aura redémarré son navigateur), nous détecterons que l’extension gears est installée, et nous pourrons commencer à exploiter ses fonctionnalités.</p> <p><br></p> <h2>Activation de gears<a name="activation"></a> </h2> <p>Une fois l’extension / le plugin gears installé, nous allons pouvoir commencer à l’exploiter pour notre site.</p> <h3>Autorisation pour votre site</h3> <p>Pour cela, en premier lieu, le visiteur doit autoriser notre site à accéder aux fonctionnalités de gears. Sans cette autorisation, impossible pour vous de déposer des fichiers sur le poste utilisateur (même chose pour les autres fonctionnalités, que nous verrons au cours des prochains articles).</p> <p>Automatiquement, lors de la première tentative d’accès aux fonctionnalités exportées par google gears, l’extension demande son autorisation à l’utilisateur de votre application : vous n’avez rien de particulier à gérer à ce niveau. <br>La seule liberté qui vous est offerte, en tant que de développeur, est de pouvoir déterminer si l’utilisateur a autorisé gears pour votre site :</p> <pre><code>if (google.gears.factory.hasPermission) { // gears est autorisé pour votre site }</code></pre> <p><br></p> <h3>Instanciation des composants gears</h3> <p>Pour pouvoir utiliser la fonctionnalité de stockage de fichiers en local, qui est celle qui nous intéresse pour cet article, deux objets sont à instancier :</p> <ul> <li>Le “<code>LocalServer</code>” en lui-même, qui permet à une application Web de stocker en cache, sur le poste client, des ressources (fichiers, notamment) HTTP, et de les servir sans connexion réseau,</li> <li>Un “<code>ManagedStore</code>”, qui permet de stocker ces fichiers, tout en gérant leur mise à jour régulière et (éventuellement) automatique.</li> </ul> <p>Qu’est-ce que cela donne en terme de code ?</p> <p>Et bien, commençons par déclarer les deux variables que nous utiliserons pour la suite de nos exemples :</p> <pre><code>var localServer; var store;</code></pre> <p>Et instancions nos deux composants :</p> <pre><code>localServer = google.gears.factory.create('beta.localserver'); store = localServer.createManagedStore('test');</code></pre> <p>Tout d’abord, nous obtenons une instance de <code>LocalServer</code>, à partir de laquelle nous serons à même de stocker localement des fichiers auxquels nous accèderions normalement via une requête HTTP - requête qui sera interceptée par gears, qui renverra directement en retour le fichier stocké localement, sans effectuer l’aller-retour client/serveur. <br>Et, ensuite, nous créons au sein de notre serveur local un <code>ManagedStore</code>, qui contiendra les-dits fichiers, en se chargeant notamment de leur mise à jour.</p> <p>Le paramètre donné à la méthode <code>createManagedStore</code> est le nom de notre espace de stockage ; c’est par ce nom que nous le retrouvons d’un chargement de page à l’autre.</p> <p>A la première exécution de cette portion de code, google gears détectera que l’utilisateur n’a pas encore autorisé votre site à accéder à ses fonctionnalités, et lui affichera une demande d’autorisation, ressemblant à celle-ci :</p> <p><a title="gears-asking-for-permission.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="gears-asking-for-permission.png" class="lazy" data-original="/public/google-gears/faster-loading/.gears-asking-for-permission_m.png" width="366px" height="240px"><noscript><img src="/public/google-gears/faster-loading/.gears-asking-for-permission_m.png" style="" alt="gears-asking-for-permission.png" width="366px" height="240px"></noscript></a> <em>(Le nom “http://tests” correspondant à celui de ma machine de développement sera naturellement remplacé par celui de votre site)</em></p> <p>Vous ne pourrez exploiter les fonctionnalités de Google Gears au sein de votre application que si votre utilisateur a autorisé celle-ci à accéder à l’extension… ce qui signifie qu’<strong>il vous faudra former vos utilisateurs</strong>, en leur expliquant ce qu’est cette boite de dialogue, et ce qu’elle leur apportera ! <br>(On répète tellement souvent qu’”<em>il ne faut pas cliquer sur „oui j’accepte</em>”” sans savoir de quoi il s’agit que ça commence à être compris… Ici, nous souhaitons justement que l’utilisateur accepte… L’expliquer - et expliquer pourquoi, cette fois, exceptionnellement, il faut dire “Oui” - ne peut être que bénéfique !)</p> <p><br></p> <h2>Stocker les fichiers statiques sur le poste client<a name="stockage-client"></a> </h2> <p>Alors… Nous avons vu comment indiquer à nos utilisateurs comment installer le plugin google gears ; nous avons vu comment obtenir des instances de classes <code>LocalServer</code> et <code>ManagedStore</code> ; et nous avons vu que, lors de la première utilisation de notre application, l’utilisateur devait autoriser google gears pour notre site. <br>Voyons maintenant (enfin ? ) comment stocker sur le poste client les fichiers statiques liés à notre application.</p> <p><br></p> <h3>ManagedStore et fichier manifest</h3> <p>Pour déterminer quels sont les fichiers à télécharger et à stocker en local, le <code>ManagedStore</code> se base sur un fichier texte, contenant un objet au format <a href="http://en.wikipedia.org/wiki/JSON">JSON</a>, contenant la liste des URLs des fichiers, ainsi qu’un numéro de version. <br>Ce fichier est dit fichier “manifest”, et souvent nommé “<code>manifest.json</code>”.</p> <p>Dans le cas de notre application, le fichier manifest serait le suivant :</p> <pre> { "betaManifestVersion": 1, "version": "0.1", "entries": [ {"url": "css/style.css"}, {"url": "js/lib/gears_init.js"}, {"url": "js/lib/prototype.js"}, {"url": "js/lib/scriptaculous/builder.js"}, {"url": "js/lib/scriptaculous/effects.js"}, {"url": "js/lib/scriptaculous/dragdrop.js"}, {"url": "js/lib/scriptaculous/controls.js"}, {"url": "js/application.js"}, {"url": "js/gestion-gears.js"}, {"url": "img/error.png"}, {"url": "img/tick.png"} ] } </pre> <p>Le principe est simple :</p> <ul> <li> <code>betaManifestVersion</code> correspond au numéro de version du format de fichier manifest utilisée ; actuellement, il n’existe qu’une version de format pour ce fichier ; on indique donc toujours <code>1</code> comme valeur.</li> <li> <code>version</code> correspond au numéro de version du fichier en lui-même : pour déterminer si les fichiers stockés localement doivent être mis à jour, c’est sur ce numéro de version que le <code>ManagedStore</code> se basera. Autrement dit, à chaque fois que vous voudrez que les fichiers stockés localement sur les postes de vos utilisateurs soient mis à jour, il faudra modifier ce numéro de version. <strong>Attention : si vous ne modifiez pas ce numéro de version, il n’y aura pas de mise à jour effectuée !</strong> </li> <li>Enfin, <code>entries</code> contient la liste de tous les fichiers qui devront être téléchargées, puis stockés sur les postes de vos utilisateurs.</li> </ul> <p>Une fois ce fichier créé et déposé sur votre serveur, vous devez indiquer à l’instance d’objet <code>ManagedStore</code> avec laquelle vous travaillez qu’elle devra utiliser ce fichier. <br>Pour cela, affectez son chemin à l’attribut <code>manifestUrl</code> de l’instance d’objet <code>ManagedStore</code> :</p> <pre><code>store.manifestUrl = 'manifest.json';</code></pre> <p>Et enfin, pour rapatrier en local l’ensemble des fichiers renseignés dans le fichier manifest, il vous faudra utiliser la méthode <code>checkForUpdate</code> de l’instance d’objet <code>ManagedStore</code> :</p> <pre><code>store.checkForUpdate();</code></pre> <p>Il est à noter que cette méthode n’est normalement à appeler qu’une seule fois, pour forcer la récupération en local des fichiers au moment où l’utilisateur a activé l’utilisation de gears pour votre application.</p> <p>A partir de là, votre application ne devrait plus effectuer de requête HTTP pour charger ces fichiers statiques : une fois qu’ils auront été stockés en local, c’est la version locale qui sera utilisée - du moins, tant que le numéro de version dans le fichier manifest n’aura pas été modifié.</p> <p>Pour rappel, au niveau du code Javascript mis en place, tout tient en quelques lignes :</p> <pre><code>var localServer = google.gears.factory.create('beta.localserver'); var store = localServer.createManagedStore('test'); store.manifestUrl = 'manifest.json'; store.checkForUpdate();</code></pre> <p>C’est-à-dire :</p> <ul> <li>Instanciation de la classe <code>LocalServer</code>,</li> <li>Instanciation de la classe <code>ManagedStore</code>,</li> <li>Renseignement du chemin vers le fichier manifest contenant la liste des fichiers statiques à stocker en local,</li> <li>Et lancement de la mise à jour.</li> </ul> <p><br></p> <h3>Résultat</h3> <p>Nous avions montré un peu plus haut une capture d’écran de l’onglet Réseau de <a href="http://getfirebug.com/">Firebug</a>, montrant les temps chargements respectifs de chacun des éléments de la page. <br>Une fois les fichiers statiques stockés en local, voici ce que nous obtenons :</p> <p><a title="firebug-network-with-gears-activated.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="firebug-network-with-gears-activated.png" class="lazy" data-original="/public/google-gears/faster-loading/.firebug-network-with-gears-activated_m.png" width="448px" height="178px"><noscript><img src="/public/google-gears/faster-loading/.firebug-network-with-gears-activated_m.png" style="" alt="firebug-network-with-gears-activated.png" width="448px" height="178px"></noscript></a></p> <p>Autrement dit, comme nous le souhaitions :</p> <ul> <li>Nous n’avons plus d’autre chargement via le réseau que celui de la page en elle-même (celle-ci étant dynamique, affichant des informations stockées en base de données côté serveur, il sera plus difficile de se passer de ce chargement - ce sera l’objectif du prochain article de cette série, d’ailleurs)</li> <li>N’ayant plus aucun chargement de fichier statique via le réseau, le temps de chargement total de la page est fortement diminué (je vous laisse comparer avec le même graphique, plus haut, qui incluait les temps de chargements des fichiers JS et CSS)</li> </ul> <p><br></p> <h3>Suivre la mise à jour d’un ManagedStore</h3> <p>L’objet <code>ManagedStore</code> nous permet de brancher des méthodes qui seront appelées lors de trois types d’évènements :</p> <h4>oncomplete</h4> <p>La fonction branchée sur ce gestionnaire sera appelée lorsque la mise à jour d’un <code>ManagedStore</code> sera terminée.</p> <p>Elle reçoit en paramètre un objet contenant une propriété <code>newVersion</code>, correspond au nouveau numéro de version indiqué dans le fichier manifest.</p> <h4>onprogress</h4> <p>La fonction branchée ici sera appelée (de manière générale, plusieurs fois : une fois pour chaque fichier mis à jour) pendant le processus de mise à jour du <code>ManagedStore</code>.</p> <p>Elle reçoit en paramètre un objet porteur de deux attributs :</p> <ul> <li> <code>filesComplete</code> : le nombre de fichiers mis à jour jusqu’à présent.</li> <li> <code>filesTotal</code> : le nombre total de fichiers à mettre à jour.</li> </ul> <h4>onerror</h4> <p>La fonction branchée sur ce gestionnaire sera appelée en cas d’erreur pendant le processus de mise à jour.</p> <p>Elle reçoit en paramètre un objet contenant la propriété message, contenant un message d’erreur sous forme textuelle.</p> <h4>Utiliser ces gestionnaires</h4> <p>Il est habituel de brancher un gestionnaire sur <code>onprogress</code> et <code>oncomplete</code>, de manière à pouvoir indiquer à l’utilisateur qu’une mise à jour est en cours, et où elle en est. <br>Typiquement, cela se fait à l’aide d’une portion de code de la forme suivante, placée après le renseignement du chemin vers le fichier manifest :</p> <p>Pour afficher un message en fin de vérification de présence d’une mise à jour :</p> <pre><code>store.oncomplete = function(details) { if (!details.newVersion.blank()) { $('debug').innerHTML += "Mise à jour effectuée ; version : " + details.newVersion + "&lt;br /&gt;"; } else { $('debug').innerHTML += "Pas de nouvelle version...&lt;br /&gt;"; } };</code></pre> <p>(En supposant que vous avez quelque part sur votre page un élément d’identifiant “<code>debug</code>”, et que vous avez, tout comme moi pour mon exemple, inclut le framework prototype, qui met à votre disposition la fonction <code>$</code> comme alias vers <code>document.getElementById</code>)</p> <p>Pour la mise à jour en train de s’effectuer, à présent, cette portion affichera la nombre de fichiers mis à jours, et le nombre de fichiers restant à récupérer depuis le serveur :</p> <pre><code>store.onprogress = function(details){ $('debug').innerHTML += "Récupération des fichiers : " + details.filesComplete + ' / ' + details.filesTotal + "&lt;br /&gt;"; };</code></pre> <p>Et, finalement, pour afficher un message en cas d’erreur à la récupération d’une mise à jour :</p> <pre><code>store.onerror = function(error) { $('debug').innerHTML += "Erreur : " + error.message + "&lt;br /&gt;"; };</code></pre> <p>Et voici le genre de résultat que ça permet d’obtenir sur notre application de tests :</p> <p><a title="gears-after-update-1.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="gears-after-update-1.png" class="lazy" data-original="/public/google-gears/faster-loading/.gears-after-update-1_m.png" width="448px" height="428px"><noscript><img src="/public/google-gears/faster-loading/.gears-after-update-1_m.png" style="" alt="gears-after-update-1.png" width="448px" height="428px"></noscript></a></p> <p>Bien entendu, à vous d’améliorer l’aspect de tout cela - typiquement, en rajoutant une barre de chargement ; discrète, dans l’idéal : la mise à jour se faisant en arrière-plan, il serait dommage de bloquer tout l’écran pour indiquer que quelque chose se passe ^^</p> <p>Note : La détection de mise à jour se fait sur le numéro de version du fichier manifest, et non fichier par fichier. Lors d’une mise à jour, c’est donc l’ensemble des fichiers décris par le fichier manifest qui sont re-téléchargés, et non uniquement ceux qui ont réellement été modifiés.</p> <p><br></p> <h2>Aller plus loin</h2> <p>Avant de terminer cet article par un rassemblage de tous les morceaux de code présentés jusqu’à présent, allons plus loin sur quelques points spécifiques.</p> <p><br></p> <h3>Forcer la détection de mise à jour</h3> <p>Nous avons dit plus haut qu’utiliser un objet de type <code>ManagedStore</code> pour le stockage local permettait de disposer de mises à jour automatiques des fichiers locaux : régulièrement, gears va vérifier si le numéro de version indiqué dans le fichier manifest est différent de celui qui a été mémorisé en local.</p> <p>Si le numéro de version a changé, les fichiers listés dans le fichier manifest seront téléchargés, et l’utilisateur travaillera avec cette nouvelle version lors du prochain chargement de la page (la mise à jour des fichiers locaux n’est prise en compte que lors d’un rechargement de page : gears ne peut pas “débrancher” l’ancienne version des fichiers, et “re-brancher” la nouvelle version à la place sans recharger la page - on peut comprendre les difficultés et les problèmes que cela pourrait causer).</p> <p>Le <code>ManagedStore</code> vérifie périodiquement l’existence de mises à jour (en pratique, il semblerait que ce soit fait quelques secondes après chaque chargement de page), et vous n’avez pas à effectuer la vérification de mise à jour vous-même. <br>Au pire, il peut vous arriver de vouloir forcer la vérification de mise à jour (éventuellement, dans le cas d’une application qui n’entraine aucun rechargement de page, ou si vous tenez vraiment à ce que vos utilisateurs aient une version à jour).</p> <p>Dans ce cas, il vous suffit d’appeler la méthode <code>checkForUpdate</code> de votre instance de <code>ManagedStore</code>, comme nous l’avions fait plus haut pour forcer le téléchargement des fichiers lors de l’activation du stockage local :</p> <pre><code>store.checkForUpdate();</code></pre> <p>Gears se chargera tout seul de vérifier la version indiquée dans le fichier manifest, et de, au besoin, rapatrier les fichiers pour les stocker, en version à jour, en cache local.</p> <p>Attention : Encore une fois, pour qu’une mise à jour se fasse, il est impératif que le numéro de version indiqué dans le fichier manifest soit différent de celui qui s’y trouvait lors de la vérification précédente ! <br>(Et ce même si vous utilisez manuellement la méthode <code>checkForUpdate</code> ! )</p> <p><br></p> <h3>Désactiver (momentanément) l’utilisation du stockage local</h3> <p>Pour je ne sais quelle raison (<em>rappel : c’est vous qui avez une application que vous voulez rendre utilisable hors-ligne ^^</em> ), vous pouvez vouloir désactiver momentanément l’utilisation du stockage local.</p> <p>Cela est possible en exploitant la propriété <code>enabled</code> de votre instance de <code>ManagedStore</code> : en la passant à <code>false</code>, au prochain chargement de la page, ce ne seront plus les fichiers stockés en local qui seront utilisés, mais ceux téléchargés depuis le serveur :</p> <pre><code>store.enabled = false;</code></pre> <p>Et pour recommencer à utiliser les fichiers stockés localement, il suffit d’inverser la valeur de la même propriété :</p> <pre><code>store.enabled = true;</code></pre> <p>Un point intéressant à noter : lorsque vous recommencez à utiliser un stockage local, vous utilisez la version actuellement stockée en local des fichiers : ils ont été conservés par gears. <br>En particulier, cela signifie qu’ils ne seront pas retéléchargés, et donc, pas mis à jour, si le numéro de version dans le fichier manifest n’a pas été modifié !</p> <p><br></p> <h3>Désactiver gears pour votre site</h3> <p>Lors de vos tests, il vous sera certainement nécessaire, un bon nombre de fois, de “repartir de zéro”, comme si vous n’aviez jamais autorisé gears pour votre site… <br>Hors, nous venons de voir que désactiver un <code>ManagedStore</code> ne le purgeait pas.</p> <p>Finalement, la meilleure façon de recommencer au tout début est de révoquer l’autorisation que vous aviez donné à gears de fonctionner pour votre site ; de la sorte, à la prochaine tentative d’utilisation d’une des fonctionnalités fournies par cette extension, il vous faudra re-donner votre autorisation, et, entre temps, tout ce qui avait été stocké en local aura été purgé.</p> <p>Pour cela, rendez-vous dans le menu “options” de votre navigateur (ou équivalent), pour ouvrir la boite de dialogue “<em>Google Gears Settings</em>”. <br>Celle-ci affiche la liste des sites qui ont le droit d’exploiter les fonctionnalités de gears :</p> <p><a title="gears-settings.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="gears-settings.png" class="lazy" data-original="/public/google-gears/faster-loading/.gears-settings_m.png" width="426px" height="448px"><noscript><img src="/public/google-gears/faster-loading/.gears-settings_m.png" style="" alt="gears-settings.png" width="426px" height="448px"></noscript></a></p> <p>Le votre est forcément dans cette liste, puisque vous l’avez autorisé plus haut. <br>Cliquez sur le lien “remove” de la ligne correspondant à votre site, validez… Et voila, vous repartez sur des bases vierges : lors de la prochaine utilisation d’une fonctionnalité fournie par gears, l’extension demandera à nouveau votre autorisation.</p> <p>(Et cette méthode est sans le moindre doute plus rapide que de désinstaller l’extension, redémarrer le navigateur pour que cette désinstallation soit prise en compte, réinstaller l’extension, et redémarrer le navigateur à nouveau pour que la réinstallation soit prise en compte ^^ )</p> <p><br></p> <h2>Recollons les morceaux</h2> <p>Nous avons vu les concepts permettant d’exploiter une partie des fonctionnalités offertes par Google Gears, pour stocker en local, sur le poste client, les fichiers statiques de notre application, ce qui était le but de cet article.</p> <p>Avant de terminer, je pense qu’il serait nécessaire de reprendre quelques portions de code en les regroupant en méthodes utilisables, pour que vous puissiez voir en un seul coup d’oeil comment le tout fonctionne…</p> <p><br></p> <h3>Squelette de page HTML</h3> <p>Pour commencer, l’application de test utilisée ici est composée d’une seule page HTML, découpée en plusieurs parties :</p> <ul> <li>liens de contrôle de gears</li> <li>affichage d’une liste de notes issues de la base de données (côté serveur)</li> <li>formulaire permettant d’ajouter une note</li> <li>et enfin, zone de débug</li> </ul> <p>En somme :</p> <pre><code>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt; &lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt; &lt;head&gt; &lt;title&gt;Google Gears&lt;/title&gt; &lt;script type="text/javascript" src="js/lib/gears_init.js"&gt;&lt;/script&gt; &lt;link href="css/style.css" media="screen" rel="stylesheet" type="text/css" /&gt; &lt;script type="text/javascript" src="js/lib/prototype.js"&gt;&lt;/script&gt; &lt;script type="text/javascript" src="js/lib/scriptaculous/builder.js"&gt;&lt;/script&gt; &lt;script type="text/javascript" src="js/lib/scriptaculous/effects.js"&gt;&lt;/script&gt; &lt;script type="text/javascript" src="js/lib/scriptaculous/dragdrop.js"&gt;&lt;/script&gt; &lt;script type="text/javascript" src="js/lib/scriptaculous/controls.js"&gt;&lt;/script&gt; &lt;script type="text/javascript" src="js/application.js"&gt;&lt;/script&gt; &lt;script type="text/javascript" src="js/gestion-gears.js"&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;?php $notes = array(); // TODO récupérer les notes depuis la DB (PHP) // TODO gérer l'ajout d'une nouvelle note en DB depuis les saisies dans le formulaire (PHP) ?&gt; &lt;div id="control"&gt; &lt;a href="http://gears.google.com/?action=install&amp;amp;name=&lt;?php echo rawurlencode('Google Gears, Tutorial'); ?&gt;&amp;amp;message=&lt;?php echo rawurlencode('Installez Google Gears pour accélérer le chargement des fichiers statiques de votre application !'); ?&gt;&amp;amp;return=&lt;?php echo rawurlencode('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME']); ?&gt;&amp;amp;icon_src=&lt;?php echo rawurlencode('http://tests/gears/faster-loading/application/img/icon-install-gears.png'); ?&gt;" id="install" style="display: none;"&gt;Installer Gears&lt;/a&gt; &lt;a href="#" id="activate" onclick="activateGears(); return false;" style="display: none;"&gt;Activer Gears&lt;/a&gt; &lt;a href="#" id="maj" onclick="majGears(); return false;" style="display: none;"&gt;Forcer la recherche de MAJ&lt;/a&gt; &lt;a href="#" id="deactivate" onclick="deactivateGears(); return false;" style="display: none;"&gt;Désactiver Gears&lt;/a&gt; &lt;/div&gt; &lt;?php foreach ($notes as $note) : ?&gt; &lt;p class="note"&gt; Auteur : &lt;?php echo htmlspecialchars($note-&gt;auteur); ?&gt; &lt;br /&gt;Date : &lt;?php echo htmlspecialchars($note-&gt;date); ?&gt; &lt;br /&gt;Texte : &lt;?php echo htmlspecialchars($note-&gt;texte); ?&gt; &lt;/p&gt; &lt;?php endforeach ; ?&gt; &lt;form method="post" action="" onsubmit="return validation();"&gt; &lt;p&gt; Auteur : &lt;input type="text" name="auteur" id="auteur" maxlength="64" value="&lt;?php echo htmlspecialchars($auteur); ?&gt;" /&gt; &lt;br /&gt;Date : &lt;input type="text" name="date" id="date" maxlength="10" value="&lt;?php echo htmlspecialchars($date); ?&gt;" /&gt; &lt;br /&gt;Texte : &lt;br /&gt;&lt;textarea name="texte" id="texte" rows="5" cols="50"&gt;&lt;?php echo htmlspecialchars($texte); ?&gt;&lt;/textarea&gt; &lt;br /&gt;&lt;input type="submit" value="Hop !" name="ok" /&gt; &lt;/p&gt; &lt;/form&gt; &lt;div id="debugTimer"&gt;&lt;/div&gt; &lt;div id="debug"&gt;&lt;/div&gt; &lt;/body&gt; &lt;/html&gt;</code></pre> <p>Je passe sur les portions de code PHP : il ne s’agit pas du point important pour cet article… Vous trouverez le tout en pièce jointe à ce billet (Cf en bas de la page)</p> <p>A noter : les liens en haut de la page, qui sont tous masqués initialement, et que nous rendrons visible à partir du code Javascript, en détectant si gears est installé ou non, autorisé ou non, …</p> <p><br></p> <h3>LocalServer et ManagedStore</h3> <p>Pour les besoins de notre application, nous avons besoin de créer une instance de classe <code>LocalServer</code>, et une instance d’objet <code>ManagedStore</code>, comme vu plus haut.</p> <p>Elles seront mémorisées au sein des deux variables suivantes :</p> <pre><code>var localServer; var store;</code></pre> <p>L’instanciation des classes en question viendra plus tard.</p> <p>Note : dans un cas réel, vous encapsuleriez peut-être tout ça dans une autre classe, plutôt que de tout fourrer dans l’espace de nom global… A vous de voir : nous ne sommes ici pas dans un cas réel ^^</p> <p><br></p> <h3>Actions au chargement de la page</h3> <p>Au chargement de la page, plusieurs actions sont à gérer :</p> <ul> <li>Détecter si l’extension / le plugin Google gears est installé</li> <li>Si non, afficher le lien menant à la page d’installation</li> <li>Si oui, <ul> <li>Détecter si l’utilisateur a donné à gears l’autorisation de fonctionner pour notre site</li> <li>Si non, afficher le lien permettant d’activer Gears pour notre site</li> <li>Si oui, <ul> <li>Détecter si le <code>ManagedStore</code> est actif</li> <li>Si non, afficher le lien permettant de l’activer</li> <li>Si oui… c’est tout : notre application utilise déjà les fichiers stockés localement - ou est en train de les télécharger si c’est le premier affichage de la page depuis l’activation de gears.</li> </ul> </li> </ul> </li> </ul> <p>Concrètement, tout ceci peut se traduire par la portion de code suivante :</p> <pre><code>Event.observe(window, 'load', function () { if (!window.google || !google.gears) { $('install').show(); } else { if (google.gears.factory.hasPermission) { $('debug').innerHTML += "Permissions : OK&lt;br /&gt;"; createStore(); if (store.enabled) { $('debug').innerHTML += "Store : actif&lt;br /&gt;"; $('maj').show(); $('deactivate').show(); } else { $('debug').innerHTML += "Store : non actif&lt;br /&gt;"; $('activate').show(); } } else { $('debug').innerHTML += "Permissions : NON OK&lt;br /&gt;"; $('activate').show(); } } });</code></pre> <p>Vous noterez que j’ai choisir d’utiliser la méthode <code>Event.observe</code> du <a href="http://www.prototypejs.org/">Framework prototype</a> sur l’événement “<em>load</em>” pour que ma portion de code soit appelée une fois l’intégralité de la page chargée, afin d’être sûr que tous les éléments du DOM soient présents avant de commencer à exécuter du code.</p> <p>Un autre point : pour simplifier les choses, la page HTML inclut de base tous les liens, masqués, et nous affichons en Javascript ceux qui nous sont nécessaires. Dans l’idéal, il serait probablement possible de n’inclure aucun lien dans la page, et de les créer à la volée… Ici encore, il y a une différence entre une mini-application de tests, et un cas réel !</p> <p><br></p> <h3>Créer une instance de ManagedStore</h3> <p>En local, nos fichiers statiques seront stockés au sein d’un <code>ManagedStore</code>. <br>Pour pouvoir obtenir une instance de cette classe, comme vu plus haut, il faut passer par une instance de <code>LocalServer</code> :</p> <pre><code>var createStore = function () { localServer = google.gears.factory.create('beta.localserver'); store = localServer.createManagedStore('test'); store.manifestUrl = 'manifest.json'; store.oncomplete = function(details) { if (!details.newVersion.blank()) { $('debug').innerHTML += "Mise à jour effectuée ; version : " + details.newVersion + "&lt;br /&gt;"; $('debug').innerHTML += "=&amp;gt; Recharger la page&lt;br /&gt;"; } else { $('debug').innerHTML += "Pas de nouvelle version...&lt;br /&gt;"; } }; store.onerror = function(error) { $('debug').innerHTML += "Erreur : " + error.message + "&lt;br /&gt;"; }; store.onprogress = function(details){ $('debug').innerHTML += "Récupération des fichiers : " + details.filesComplete + ' / ' + details.filesTotal + "&lt;br /&gt;"; }; }; // createStore</code></pre> <p>Nous profitons de l’instanciation de notre espace de stockage pour brancher nos gestionnaires sur les événements affectant son cycle de vie, notamment pendant les éventuelles mises à jour des fichiers stockés localement.</p> <p><br></p> <h3>Activation de gears pour notre site</h3> <p>Lors du clic sur le lien d’activation de gears, nous devons :</p> <ul> <li>créer le <code>ManagedStore</code> qui servira à stocker localement les fichiers distants,</li> <li>forcer une mise à jour de celui-ci, pour que les fichiers soient récupérés depuis le serveur lors de la première activation de gears.</li> </ul> <p>Nous en profitons pour nous assurer que le store soit bien activé, en passant à <code>true</code> sa propriété <code>enabled</code> :</p> <pre><code>var activateGears = function () { $('debug').innerHTML += "Activation de google gears&lt;br /&gt;"; createStore(); store.enabled = true; majGears(); }; // activateGears</code></pre> <p>Cette méthode sera appelée lorsque l’utilisateur activera Gears pour notre site (ce qui explique qu’il faille forcer la MAJ), ainsi que lorsque l’utilisateur ré-activera Gears après l’avoir désactivé (ce qui explique qu’il faille nous assurer que l’attribut <code>enabled</code> est à <code>true</code> : il aura été basculé à <code>false</code> lors de la désactivation).</p> <p><br></p> <h3>Mise à jour des fichiers stockés localement</h3> <p>La mise à jour des fichiers stockés localement est des plus simple : il suffit de faire appel à la méthode <code>checkForUpdate</code> de notre instance de <code>ManagedStore</code> :</p> <pre><code>var majGears = function () { if (store.enabled) { $('debug').innerHTML += "Lancement de la détection de Mises à jour...&lt;br /&gt;"; store.checkForUpdate(); } else { $('debug').innerHTML += "Store désactivé =&amp;gt; Pas de vérification de MAJ...&lt;br /&gt;"; } }; // activateGears</code></pre> <p>Encore une (dernière ?) fois, je rappelle que même si des fichiers ont été mis à jours sur le serveur, la mise à jour ne se fera que si le numéro de version dans le fichier manifest a été modifié !</p> <p><br></p> <h3>Fichier manifest</h3> <p>En parlant de fichier manifest… Il s’agit bien entendu du même que celui présenté plus haut, mais le reproduire ici ne peut faire de mal à personne :</p> <pre> { "betaManifestVersion": 1, "version": "0.1", "entries": [ {"url": "css/style.css"}, {"url": "js/lib/gears_init.js"}, {"url": "js/lib/prototype.js"}, {"url": "js/lib/scriptaculous/builder.js"}, {"url": "js/lib/scriptaculous/effects.js"}, {"url": "js/lib/scriptaculous/dragdrop.js"}, {"url": "js/lib/scriptaculous/controls.js"}, {"url": "js/application.js"}, {"url": "js/gestion-gears.js"}, {"url": "img/error.png"}, {"url": "img/tick.png"} ] } </pre> <p>Je rappelle :</p> <ul> <li>le numéro de version du format de fichier,</li> <li>le numéro de version du fichier en lui-même,</li> <li>et la liste des fichiers que gears doit stocker en local.</li> </ul> <p><br></p> <h3>Désactiver gears</h3> <p>Après ce que j’ai dit quelques lignes plus haut, la désactivation - temporaire - de gears, pour utiliser à nouveau des fichiers téléchargés depuis le serveur à chaque chargement de page, ne devrait pas poser de problème :</p> <pre><code>deactivateGears = function () { $('debug').innerHTML += "Désactivation de gears...&lt;br /&gt;"; $('debug').innerHTML += "=&amp;gt; Recharger la page&lt;br /&gt;"; store.enabled = false; }; // deactivateGears</code></pre> <p>En effet, il s’agit uniquement de mettre à <code>false</code> le flag <code>enabled</code> du <code>ManagedStore</code> utilisé pour stocker localement les fichiers.</p> <p><br></p> <h2>Avant de passer à la suite…</h2> <p>Cet article était le premier d’une série ayant pour objectif de vous permettre de développer une application continuant à fonctionner hors-connexion, en utilisant Google gears. <br>Il nous a permis de découvrir les premiers concepts nécessaires à l’utilisation de Gears ; en particulier, comment installer l’extension, et détecter si l’utilisateur l’a autorisée à fonctionner pour notre site.</p> <p>Bien entendu, stocker localement, sur le poste client, les fichiers statiques n’est qu’un premier pas : débranchez votre cable réseau, et vous réaliserez vite qu’il reste encore du travail à effectuer !</p> <p>Avant de vous donner rendez-vous pour le second article de cette série, j’aimerais attirer votre attention sur un point : le concept offert par Gears - pouvoir continuer à travailler hors-ligne avec une application Web - est peut-être “révolutionnaire”… mais il est surtout complexe pour vos utilisateurs : il faut installer une extension / un plugin, il faut autoriser l’utilisation de celle-ci par votre site, il faut comprendre la notion de synchronisation, … <br>Pour cette raison, j’estime que Gears ne doit pas devenir un pré-requis pour votre site : vous pouvez en utiliser les fonctionnalités, et proposer à vos utilisateurs n’en bénéficier… Mais votre site <strong>doit</strong> rester fonctionnel pour les utilisateurs n’ayant pas Gears installé ! <br>Dans la même logique, Gears est sûrement plus adapté à un Back-office, réservé à un nombre restreint d’utilisateurs que vous pouvez facilement former, qu’à un Front-office ouvert à des milliers de visiteurs !</p> <p><br></p> <div class="footnotes"> <h4>Notes</h4> <p>[<a href="#rev-wiki-footnote-1">1</a>] Le travail hors-ligne sous Google Docs étant limité aux documents texte uniquement - du moins pour l’instant</p> <p>[<a href="#rev-wiki-footnote-2">2</a>] Cf <a href="http://code.google.com/support/bin/answer.py?answer=69201&amp;topic=11629">What browsers and operating systems are supported by Google Gears? </a></p> <p>[<a href="#rev-wiki-footnote-3">3</a>] Si votre application est mise à jour plus souvent qu’elle n’est utilisée… C’est dommage ^^</p> <p>[<a href="#rev-wiki-footnote-4">4</a>] J’utiliserai parfois le terme “extension” (dénomination usuelle sous Firefox), et parfois le terme “plugin” (qui est plus habituel sous Internet Explorer). Les deux, pour cet article, sont à considérer comme synonymes.</p> </div> http://blog.pascal-martin.fr/post/Google-Gears-accelerer-chargement-application-cache-fichiers-statiques#comment-new-form http://blog.pascal-martin.fr/post/Google-Gears-accelerer-chargement-application-cache-fichiers-statiques#comment-new-form Optimiser la comparaison de deux listes en PHP http://blog.pascal-martin.fr/post/Optimiser-la-comparaison-de-deux-listes-en-PHP?utm_source=rss2&utm_medium=feed&utm_campaign=Optimiser-la-comparaison-de-deux-listes-en-PHP http://blog.pascal-martin.fr/post/Optimiser-la-comparaison-de-deux-listes-en-PHP Sat, 08 Dec 2007 21:00:00 +0100 Pascal MARTIN Xdebugperformanceprofiling <p>Vous avez sûrement déjà rencontré une situation de ce genre :</p> <ul> <li>Vous avez deux listes d’entités,</li> <li>et vous voulez savoir combien des entités présentes dans la première liste sont aussi présentes dans la seconde.</li> </ul> <p>Un exemple typique serait la question suivante :</p> <p>Combien de mots présents dans une liste A sont aussi présents dans une liste B ?</p> <p>Pour répondre à cette question, en utilisant une implémentation PHP, vous avez plusieurs solutions, plus ou moins naïves - et dépendant de votre niveau de connaissance du langage et de sa bibliothèque de fonctions.</p> <p>Certaines de ces solutions sont rapides ; d’autres sont extrêmement lentes !</p> <p>Au cours de cet article, nous étudierons plusieurs solutions, pour en arriver à la conclusion que la solution naïve, la première à laquelle un débutant penserait peut-être, est loin d’être optimale…</p> <p><br></p> <h2>Principe</h2> <p>Je présenterai plusieurs solutions permettant de déterminer combien de mots présents dans une liste A sont aussi présents dans une liste B.</p> <p>Ensuite, je fournirai les temps d’exécutions obtenus pour chacune de ces solutions, avec un grand nombre d’appels, afin de constater les différences de rapidité entre chaque solution.</p> <p>Je terminerai par expliquer pour certains types de solutions sont plus rapides que les autres… Et pourquoi certaines implémentations sont à bannir.</p> <p><br></p> <h3>Scénarios</h3> <p>Voici le cadre dans lequel les tests menés ci-dessous on été réalisés :</p> <h4>Machine</h4> <p>Tous mes tests ont été réalisés sur la machine suivante :</p> <ul> <li>Core 2 Duo E6600 2.4 GHz</li> <li>2Go de RAM</li> <li>Disque dur SATA-2 10.000 rpm</li> <li>PHP 5.2 utilisé en CLI</li> <li>Extension Xdebug 2.0.1 utilisée pour le profiling</li> </ul> <p><br></p> <h4>Jeux de données</h4> <p>Pour ces tests, le jeu de données utilisé est une extraction aléatoire<sup>[<a href="#wiki-footnote-1">1</a>]</sup> du flux RSS de ce site.</p> <p>Pour chaque article du flux, j’ai supprimé le maximum de données inutiles (codes sources, par exemple), afin de ne conserver, globalement, que les textes.</p> <p>Vous trouverez en pièce jointe à cet article une archive contenant les script utilisés, ainsi que les données de tests.</p> <p><br></p> <h4>Deux types de tests</h4> <p>Mon premier scénario de test est le suivant : Recherche du nombre de mots présents à la fois dans un article et un second.</p> <p>Et le second est une généralisation du premier : Recherche dans N articles de tous les mots présents dans chacun des articles. <br>Plus précisément : recoupement du premier article avec tous les autres, recoupement du second article avec tous les autres, recoupement du troisième article avec tous les autres, …</p> <p><br></p> <h3>Préparation des données</h3> <p>Voici les données utilisées pour mes tests :</p> <h4>Chargement des listes de mots</h4> <p>J’ai sur ma machine une série de fichiers nommées “article-X.txt”, correspondant à chacun à un article de ce blog.</p> <p>Chaque fichier est constitué de la liste des mots, uniques, du billet correspondant, à raison de un mot par ligne. <br>Ces fichiers peuvent être chargés à l’aide de la fonction suivante :</p> <pre><code>function getArticles() { for ($i=0 ; $i&lt;=12 ; $i++) { $articles[$i] = file("data/textes-articles/article-$i.txt", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); } return $articles; }</code></pre> <p>La variable <code>$articles</code>, utilisée par la suite pour tous les tests est obtenue de la manière suivante :</p> <pre><code>$articles = getArticles();</code></pre> <p><br></p> <h4>Calcul nombre de mots distincts</h4> <p>Pour information, voici les nombres de mots obtenus, pour chaque article :</p> <pre> $ wc -l textes-articles/* 683 textes-articles/article-0.txt 370 textes-articles/article-10.txt 430 textes-articles/article-11.txt 163 textes-articles/article-12.txt 439 textes-articles/article-1.txt 496 textes-articles/article-2.txt 542 textes-articles/article-3.txt 674 textes-articles/article-4.txt 334 textes-articles/article-5.txt 584 textes-articles/article-6.txt 257 textes-articles/article-7.txt 500 textes-articles/article-8.txt 478 textes-articles/article-9.txt 5950 total </pre> <p><em>(Puisqu’on a stocké un mot par ligne dans chacun des fichiers)</em></p> <p><br></p> <h2>Présentation des différents modes de recherche :</h2> <p>Passons maintenant aux différents “moteurs de recherche” répondant à notre problématique…</p> <p>Pour faciliter le développement, et, surtout, les tests de performance, chacun de ces moteur de recherche a été implémenté sous forme d’une fonction indépendante, l’idée étant de pouvoir comparer les temps d’exécution de chacune d’entre elles.</p> <p><br></p> <h3>Double-parcours manuel</h3> <p>La première solution que nous allons voir est la plus naïve : celle à laquelle un développeur ne connaissant pas du tout PHP aurait probablement tendance à penser, puisqu’elle ne requiert aucune connaissance spécifique à PHP ; juste quelques notions d’algorithmique.</p> <p>Cette portion de code répond exactement à la problématique : on parcourt tous les mots de la première liste, et, pour chacun de ces mots, on parcourt tous les mots de la seconde liste, en les comparant avec le mot courant issu de la première :</p> <pre><code>function compare_arrays_our($liste1, $liste2) { $nbr = 0; foreach ($liste1 as $mot1) { foreach ($liste2 as $mot2) { if ($mot1 == $mot2) { $nbr++; } } } return $nbr; }</code></pre> <p>Le gros problème de cette solution : elle est en <em>O(n*m)</em>… Et une double boucle telle que celle-ci, à 100% en PHP… C’est lent !</p> <p><strong>Le temps d’exécution de cette première solution est de 176 ms</strong> pour trouver le nombre de mots présents à la fois dans le premier et dans le second article.</p> <p><br></p> <h3>Parcours manuel, et appel à <code>in_array</code> </h3> <p>Voici une seconde version de ce principe, mais on la boucle interne a été remplacée par un appel à la fonction php <a href="http://www.php.net/in_array">in_array</a> :</p> <pre><code>function compare_arrays($liste1, $liste2) { $nbr = 0; foreach ($liste1 as $mot1) { if (in_array($mot1, $liste2)) { $nbr++; } } return $nbr; }</code></pre> <p>Fondamentalement, en terme d’algo, cette solution est la même que la précédente.</p> <p>Mais le remplacement d’une boucle 100% PHP par un appel à la fonction interne <code>in_array</code> améliore grandement la rapidité d’exécution : sur notre cas de test, <strong>on passe de 176 ms à 34 ms</strong> !</p> <p>Qu’est-ce que cela nous apprend ?</p> <p>Une boucle codée en PHP est beaucoup plus lente qu’une boucle codée au sein d’une fonction de la bibliothèque (codée en C, et compilée, plutôt qu’interprétée).</p> <p><br></p> <h3>Parcours manuel de Hash, et appel à <code>array_key_exists</code> </h3> <p>A présent, plutôt que d’effectuer un parcours entier de la seconde liste, voyons si on ne peut pas optimiser cela…</p> <p>Tous nos mots sont uniques, au sein de ladite liste ; on peut donc “retourner” celle-ci, pour avoir un tableau associatif dont les clefs sont les mots, et les valeurs… peu importe.</p> <p>L’avantage ? SI PHP se débrouille bien, déterminer si une clef existe dans un tableau est plus rapide que de déterminer si une valeur existe : au sein d’un tableau non trié, il faut faire un O(n) pour trouver si une valeur existe… <br>Alors que si le tableau est organisé en mémoire sous forme d’un <a href="http://fr.wikipedia.org/wiki/Arbre_binaire_de_recherche">Arbre Binaire de Recherche</a>, nous pouvons statuer sur l’existence d’un élément en O(log(n)) dans le cas moyen.</p> <p>Avant de passer aux tests en eux-même, voici une petite fonction utilitaire, transformant une liste de mots en un tableau associatif au sein duquel les mots sont les clefs :</p> <pre><code>function toHash($liste) { $hash = array(); foreach ($liste as $mot) {$hash[$mot] = 1;} return $hash; }</code></pre> <p>Et voici une première idée d’implémentation utilisant le principe que je viens de présenter :</p> <pre><code>function compare_arrays_hash_keys($liste1, $liste2) { $hash2 = toHash($liste2); $nbr = 0; foreach ($liste1 as $mot1) { if (array_key_exists($mot1, $hash2)) { $nbr++; } } return $nbr; }</code></pre> <p><strong>Nous passons de 34 ms avec la méthode précédente à 13 ms</strong> avec celle-ci.</p> <p>Bien que nous prenions le temps de “retourner” la liste, nous avons donc divisé le temps d’exécution par pas loin de trois !</p> <p>Tester l’existence d’une clef d’un tableau, avec <code>array_key_exists</code> est plus rapide que de tester la présence d’une valeur dans ce même tableau, avec <code>in_array</code> - même s’il faut retourner le tableau pour cela !</p> <p><strong>Attention</strong> : ici, on peut se permettre cette amélioration parce que chaque mot n’est présent qu’une seule fois dans la liste ; on n’a dont jamais deux mots qui veulent la même place en clef du tableau associatif.</p> <p><br></p> <h3>Comptage du nombre d’élément dans la liste d’intersections des deux tableaux : <code>array_intersect</code> </h3> <p>Maintenant, par curiosité, voyons ce que nos pourrions faire pour totalement supprimer les parcours de tableaux côté PHP.</p> <p>Pour rappel, nous voulons compter combien de mots sont présents à la fois dans une liste A et dans une liste B…</p> <p>En PHP, pour obtenir une liste d’éléments se trouvant simultanément dans deux listes, il convient d’utiliser la fonction <a href="http://php.net/array_intersect">array_intersect</a>. <br>Et, pour compter combien d’éléments sont présents au sein de cette liste d’intersection, on peut utiliser <a href="http://php.net/count">count</a>.</p> <p>Ce qui nous donne :</p> <pre><code>function compare_arrays_intersect($liste1, $liste2) { $nbr = 0; $nbr = count(array_intersect($liste1, $liste2)); return $nbr; }</code></pre> <p>Et, avec cette solution, <strong>on descend à 1.089 ms</strong> - soit <strong>13 fois plus rapide</strong> que la solution précédente !</p> <p>Ici, le gain vient du fait que nous n’effectuons plus aucune boucle en PHP : les boucles sont réalisées en interne, par des fonctions de la bibliothèque PHP, qui ne sont pas codées en PHP, mais en langage compilé.</p> <p><br></p> <h3>Test de l’existence de clefs dans un Hash construit “à la main” : <code>isset</code> </h3> <p>Une autre solution : nous reprenons l’idée utilisée plus haut, à savoir, tenter de travailler sur les clefs d’un tableau associatif, plutôt que sur les valeurs d’une liste, en espérant que PHP gère cela intelligemment.</p> <p>Par contre, cette fois-ci, plutôt que de faire appel à <code>array_key_exists</code> pour déterminer si un mot est présent dans le tableau associatif (ie, si un mot est présent comme clef de celui-ci), nous allons utiliser la construction de langage <a href="http://php.net/isset">isset</a><sup>[<a href="#wiki-footnote-2">2</a>]</sup>.</p> <p>L’idée étant qu’appeler une fonction interne au langage sera peut-être plus rapide qu’appeler une fonction de la bibliothèque PHP - de la même manière qu’appeler une fonction de la bibliothèque PHP était plus rapide qu’appeler une fonction définie par l’utilisateur.</p> <pre><code>function compare_arrays_hash_isset($liste1, $liste2) { $hash2 = toHash($liste2); $nbr = 0; foreach ($liste1 as $mot1) { if (isset($hash2[$mot1])) { $nbr++; } } return $nbr; }</code></pre> <p>Effectivement,nous y gagnons en performances : la version basée sur l’utilisation de <code>array_key_exists</code> durait 13 ms ; celle-ci ne met<strong> plus que 0.696 ms</strong> !</p> <p>Tester l’existence d’une clef d’un tableau avec <code>isset</code> est plus rapide que de tester son existence par un appel à <code>array_key_exists</code></p> <p><strong>Attention</strong> : <code>isset</code> et <code>array_key_exists</code> ne font pas exactement la même chose ! <br>En particulier, <code>isset</code> renverra <code>false</code> si la valeur correspond à la clef demandée existe, mais vaut <code>null</code> !</p> <p><br></p> <h3>Test de l’existence de clefs dans un Hash construit via <code>array_flip</code> </h3> <p>La solution proposée juste au-dessus commence à nous donner des résultats intéressants en termes de performance… Mais il doit encore être possible de faire mieux…</p> <p>Elle commence par “retourner” la liste de un tableau associatif, dont les clefs sont les mots de chaque liste… Et ceci était fait via un appel à une fonction développée par nos soins…</p> <p>Hors, nous avons vu plus haut que ré-inventer la roue n’était pas optimal question performances… Fouillons donc un peu dans la documentation de la bibliothèque PHP, à la recherche d’une fonction qui pourrait effectuer ce “retournement” pour nous… <br>Et une telle fonction existe : <a href="http://php.net/array_flip">array_flip</a>.</p> <p>En la mettant en application, voici le code obtenu pour notre fonction de comparaison :</p> <pre><code>function compare_arrays_hash_flip_isset($liste1, $liste2) { $hash2 = array_flip($liste2); $nbr = 0; foreach ($liste1 as $mot1) { if (isset($hash2[$mot1])) { $nbr++; } } return $nbr; }</code></pre> <p>Et là, <strong>nous descendons à 0.488 ms</strong> ! Soit une fois et demie plus rapide que la version précédente, en écrivant moins de code !</p> <p>Le gain ici est l’utilisation d’une fonction de la bibliothèque PHP pour “retourner” la liste, plutôt que d’une boucle “à la main”, en PHP. Il s’agit donc encore du même type de gain que déjà rencontré plusieurs fois !</p> <p>Pour rappel, la première solution proposée demandait 176 ms pour s’exécuter ; celle-ci demande moins d’une demi-milli-seconde !</p> <p><br></p> <h2>Résultats des comparaisons</h2> <p>Mettons maintenant côtes-à-côtes les appels à chacune de ces fonctions, pour déterminer lesquelles sont performantes et lesquelles ne le sont pas.</p> <h3>Premier scénario de tests</h3> <h4>Code de test</h4> <p>Voici le premier scénario de tests utilisé ; celui dont les résultats ont été utilisés plus haut pour comparer chacune des solutions proposées :</p> <pre><code>$nbrIterations = 1;</code></pre> <p>Le principe est simple : on appelle <code>$nbrIterations</code> fois chacune des fonctions pour comparer les deux premiers articles :</p> <pre><code>$liste1 = $articles[0]; $liste2 = $articles[1]; for ($i=0 ; $i&lt;$nbrIterations ; $i++) { $nbr1 = compare_arrays_our($liste1, $liste2); $nbr2 = compare_arrays($liste1, $liste2); $nbr3 = compare_arrays_hash_keys($liste1, $liste2); $nbr4 = compare_arrays_intersect($liste1, $liste2); $nbr5 = compare_arrays_hash_isset($liste1, $liste2); $nbr6 = compare_arrays_hash_flip_isset($liste1, $liste2); }</code></pre> <p>Et voici les résultats obtenus :</p> <h4>Pour une itération</h4> <p><a title="compare-get-count-1.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="Comparaison de 2 articles ; 1 itération ; temps en ms" class="lazy" data-original="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-count-1_m.png" width="448px" height="158px"><noscript><img src="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-count-1_m.png" style="" alt="Comparaison de 2 articles ; 1 itération ; temps en ms" width="448px" height="158px"></noscript></a></p> <p>Les fonctions étant classées, de haut en bas, par durée d’exécution décroissante, je pense que cette capture d’écran parle d’elle-même…</p> <p><br></p> <h4>Pour 100 itérations : Callgraph</h4> <p>Et voici, pour les plus visuels d’entre vous, ce que donne le graphe d’appels :</p> <p><a title="compare-get-count-callgraph.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="Comparaison de deux articles : callgraph" class="lazy" data-original="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-count-callgraph_m.png" width="448px" height="228px"><noscript><img src="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-count-callgraph_m.png" style="" alt="Comparaison de deux articles : callgraph" width="448px" height="228px"></noscript></a></p> <p>Vous noterez que l’outil que j’utilise ne présent que les fonctions durant plus de 1% du temps d’exécution du test… <br>Et ici, seules trois des six solutions de recherche proposées sont affichées ! Parlant, n’est-ce pas ?</p> <p><br></p> <h4>En supprimant les deux méthodes les plus coûteuses, 100 itérations</h4> <p>En lançant 100 fois le test, en supprimant les deux solutions les plus coûteuses, voici ce que l’on obtient :</p> <p><a title="compare-get-count-100-only-fastests.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="Comparaison de 2 articles ; 100 itérations, en éliminant les deux solutions les plus coûteuses" class="lazy" data-original="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-count-100-only-fastests_m.png" width="448px" height="128px"><noscript><img src="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-count-100-only-fastests_m.png" style="" alt="Comparaison de 2 articles ; 100 itérations, en éliminant les deux solutions les plus coûteuses" width="448px" height="128px"></noscript></a></p> <p>Et une autre représentation graphique serait la suivante :</p> <p><a href="/public/Optimiser-Recherche-tableaux-PHP/compare-get-count-100-only-fastests-blocks.png" title="compare-get-count-100-only-fastests-blocks.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="Comparaison de 2 articles ; 100 itérations, en éliminant les deux solutions les plus coûteuses" class="lazy" data-original="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-count-100-only-fastests-blocks_m.png" width="448px" height="255px"><noscript><img src="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-count-100-only-fastests-blocks_m.png" style="" alt="Comparaison de 2 articles ; 100 itérations, en éliminant les deux solutions les plus coûteuses" width="448px" height="255px"></noscript></a></p> <p>Les tailles de chacun des blocs sont proportionnelles aux temps d’exécution de chaque méthodes dont ils portent les noms…</p> <p>Voyant ceci, quelle solution adopteriez-vous ? Ou plutôt, <strong>quelle solution n’adopteriez-vous pas ?</strong></p> <p><br></p> <h3>Second scénario de test</h3> <h4>Code de test</h4> <p>Et voici le code du second scénario :</p> <p>Ici, on compare tous les articles les uns avec les autres - et encore une fois, avec un nombre d’itérations paramétrable.</p> <pre><code>foreach ($articles as $liste2) { foreach ($articles as $liste1) { for ($i=0 ; $i&lt;$nbrIterations ; $i++) { compare_arrays_our($liste1, $liste2); } for ($i=0 ; $i&lt;$nbrIterations ; $i++) { compare_arrays($liste1, $liste2); } for ($i=0 ; $i&lt;$nbrIterations ; $i++) { compare_arrays_hash_keys($liste1, $liste2); } for ($i=0 ; $i&lt;$nbrIterations ; $i++) { compare_arrays_intersect($liste1, $liste2); } for ($i=0 ; $i&lt;$nbrIterations ; $i++) { compare_arrays_hash_isset($liste1, $liste2); } for ($i=0 ; $i&lt;$nbrIterations ; $i++) { compare_arrays_hash_flip_isset($liste1, $liste2); } } // Fin second parcours } // Fin premier parcours</code></pre> <p><em>(Oui, on aurait pu faire plus élégant, notamment sur les boucles d’itérations…)</em></p> <p>Notez que, puisque l’on compare tous les articles les uns par rapport aux autres, et qu’il y avait 13 articles dans le jeu de test, le nombre d’itérations est à multiplier par 13*13 = 169 pour obtenir le nombre d’appels à chacune des fonctions testées !</p> <p>Voici les temps d’exécution obtenus pour 10 itérations (1690 exécutions)</p> <p><a title="compare-get-count-s2.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="Comparaison de tous les articles les uns par rapport aux autres" class="lazy" data-original="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-count-s2_m.png" width="448px" height="134px"><noscript><img src="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-count-s2_m.png" style="" alt="Comparaison de tous les articles les uns par rapport aux autres" width="448px" height="134px"></noscript></a></p> <p>Les résultats obtenus sont proches des précédents : peut-être des variations mineures, mais l’idée globale est proche : ce sont toujours les mêmes implémentations qui sont les plus lentes… Et les autres qui sont les plus rapides.</p> <p><br></p> <h2>Et en retournant la liste de mots communs aux deux articles ?</h2> <p>Pour finir, voici une comparaison des mêmes fonctions, mais modifiées pour retourner la liste des mots communs aux deux articles, plutôt que uniquement leur nombre.</p> <p>Le scénario de test est le suivant :</p> <pre><code>$liste1 = $articles[0]; $liste2 = $articles[1]; for ($i=0 ; $i&lt;$nbrIterations ; $i++) { $tab1 = compare_arrays_our($liste1, $liste2); $tab2 = compare_arrays($liste1, $liste2); $tab3 = compare_arrays_hash_keys($liste1, $liste2); $tab4 = compare_arrays_intersect($liste1, $liste2); $tab5 = compare_arrays_hash_isset($liste1, $liste2); $tab6 = compare_arrays_hash_flip_isset($liste1, $liste2); }</code></pre> <p>Par sécurité, on vérifie que tous les appels ont retourné la même liste :</p> <pre><code>var_dump( array_diff( $tab1, $tab2, $tab3, $tab4, $tab5, $tab6 ) );</code></pre> <p>Et… oui :</p> <pre> array(0) { } </pre> <p>Et les résultats, sur 100 it”rations, sont les suivants :</p> <p><a title="compare-get-array-s1.png"><img src="data:image/gif;base64,R0lGODdhAQABAOcAAAAAAAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///ywAAAAAAQABAAAIBAChBQQAOw==" alt="Comparaison de 2 articles avec retour de la liste de mots en commun ; 100 itérations ; temps en ms" class="lazy" data-original="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-array-s1_m.png" width="448px" height="168px"><noscript><img src="/public/Optimiser-Recherche-tableaux-PHP/.compare-get-array-s1_m.png" style="" alt="Comparaison de 2 articles avec retour de la liste de mots en commun ; 100 itérations ; temps en ms" width="448px" height="168px"></noscript></a></p> <p>Soit le même type de graphe que ceux obtenus précédemment, lorsque nous ne retournions que les nombres de mots en commun.</p> <p>Avec une différence tout de même : les temps d’exécution sont légèrement supérieurs à ceux obtenus précédemment - mais il fallait s’y attendre : on doit construire la liste à retourner, plutôt que d’effectuer un simple comptage…</p> <p><br></p> <h2>Conclusions</h2> <p>Après avoir vu les résultats obtenus pour différents types de méthodes de comparaisons de tableaux en PHP, ou, surtout, après avoir constaté les écarts <strong>énormes</strong> entre certains types de solutions, que peut-on ajouter en conclusion, qui soit plus parlante que les graphiques présentés plus haut ?</p> <p>LE point dont il faut se souvenir, je pense, est le suivant : <strong>n’effectuez pas de parcours inutiles !</strong></p> <p>Les deux boucles <code>foreach</code> de notre première solution sont une catastrophe, d’un point de vue performances ! <br>Même si l’idée d’effectuer un double parcours n’est pas fantastique, en utilisant <code>in_array</code> pour remplacer le <code>foreach</code> interne et la comparaison, on a effectué le plus gros gain de tout cet article !</p> <p>D’où cette première conclusion :</p> <p>Ne réinventez pas la roue : <strong>utilisez à bon escient les fonctions de la bibliothèque PHP</strong>.</p> <p>Considérant qu’elles sont codées en C, et utilisées tous les jours de manière intensives, il est à parier qu’elles seront plus performantes que vos ré-implémentations en PHP.</p> <p><br> Le second plus gros gain a été réalisé en testant l’existence de clefs dans un Hash, plutôt qu’en parcourant une liste à la recherche d’une valeur. <br>Là, il s’agit de savoir que la recherche d’une clef est plus rapide que la recherche d’une valeur<sup>[<a href="#wiki-footnote-3">3</a>]</sup>.</p> <p>D’où cette seconde recommandation :</p> <p><strong>Utilisez au maximum les spécificités du langage</strong> avec lequel vous travaillez.</p> <p>Comment penser à ce genre “d’astuce” ? Souvenez-vous de vos cours d’algorithmique, lorsque vous travaillez sur les arbres binaires et/ou les tables de Hachage… <br>Même sans savoir comment sont, en interne, implémentés les tableaux associatifs de PHP, il semble probable que ce soit à l’aide d’une solution bien plus optimisée qu’une liste plate !</p> <p><br><br></p> <div class="footnotes"> <h4>Notes</h4> <p>[<a href="#rev-wiki-footnote-1">1</a>] J’allais dire “assez ancienne” (ça fait des mois que cet article est dans mes cartons et que je parviens à me décider à le terminer…), mais je suis reparti d’une version à jour avant de le publier ; donc, les données de tests correspondent à une extraction du flux RSS du site, datant de début décembre 2007.</p> <p>[<a href="#rev-wiki-footnote-2">2</a>] “Construction de langage étant une traduction de “language construct” - quelque chose de directement inclu au langage, autrement dit.</p> <p>[<a href="#rev-wiki-footnote-3">3</a>] Attention, cela dit, <code>isset</code> n’est pas non plus la solution magique à tous les problèmes ; ne serait-ce parce qu’elle renvoi <code>true</code> quand on l’appelle sur une variable valant <code>null</code>…</p> </div> http://blog.pascal-martin.fr/post/Optimiser-la-comparaison-de-deux-listes-en-PHP#comment-new-form http://blog.pascal-martin.fr/post/Optimiser-la-comparaison-de-deux-listes-en-PHP#comment-new-form