Optimiser les pages PHP
Transcription
Optimiser les pages PHP
Optimiser les pages PHP Toutes les astuces permettant d'optimiser la vitesse d'exécution de vos scripts PHP... Généralités Voici quelques règles qui vous permettront d'optimiser facilement votre script : • • • Les structures du langage sont toujours préférable aux fonctions : privilégier par exemple $var === NULL à is_null($var). Les fonctions préexistantes de PHP sont préférables aux expressions régulières : privilégier par exemple ctype_alpha($var) à eregi("^[a-z]*$", $var). Toujours privilégier des fonctions internes de PHP à des fonctions utilisateur, notamment dans les boucles et opérations sur des tableaux. Trouver la fonction adaptée Utiliser $str = file_get_contents($file); plutôt que $fp = fopen($file, 'r'); while (($line = fgets($fp)) !== FALSE) $str.= $line; fclose($fp); Evaluer les performances d'un script Voici un code très simple vous permettant de facilement évaluer le temps d'exécution d'une portion de code. function getMicroTime() { list($usec, $sec) = explode(' ', microtime()); return ((float) $usec + (float) $sec); } $timeStart = getMicroTime(); for ($i = 0; $i < 10000; $i++) { /* Nombre en fonction des performances de votre machine et du script a évaluer. */ // Portion de code a tester. } $timeEnd = getMicroTime(); // Affichage du temps d'exécution. echo ($timeEnd - $timeStart); Exemple Dans cet exemple, nous allons tenter d'optimiser un code permettant d'ôter la chaîne "[]" à la fin d'une chaine de caractère, si elle est présente. Cas 1 : $str = preg_replace('/\\[\\]$/D', '', $str); Chaîne essai essai[] Temps d'exécution 0,54s 0,74s Cas 2 : $nameLength = strlen($name); if ($name{$nameLength - 2} == '[' && $name{$nameLength - 1} == ']') $name = substr($name, 0, -2); Chaîne essai essai[] Temps d'exécution 0,51s 0,87s Cas 3 : if (substr($name, -2) == '[]') $name = substr($name, 0, -2); Chaîne essai essai[] Temps d'exécution 0,39s 0,63s Conclusions Il apparaît que la dernière méthode est la plus performante, ce était relativement prévisible, car c'est la méthode qui nécessite le moins d'opérations élémentaires. Il est toutefois surprenant que la méthode 2 soit la moins performante, mais cela s'interpréter par le fait que l'utilisation des crochets pour accéder à un caractère équivaut plus ou moins à l'utilisation de la fonction substr(). Enfin, on peut constater que la méthode 1 n'est pas trop mauvaise, malgré l'utilisation d'une expression régulière. Ainsi, il est souvent préférable d'utiliser une expression régulière plutôt que plusieurs appels à des fonctions tels que strpos() ou substr(). Au niveau de la configuration PHP Certaines directives de configuration de PHP limitent les performances. Voici la configuration recommandée pour certaines d'entre elles : register_globals à off : réduit le temps d'enregistrement des variables en début de script et l'utilisation mémoire (évite la redondance de données). De nombreux scripts relève sur cette option et ne fonctionneront pas sans. Mettre cette option à off permet d'améliorer la propreté et la sécurité de votre code PHP, tout en assurant un meilleur débuggage. register_long_arrays à off : réduit le temps d'enregistrement des variables en début de script et l'utilisation mémoire (évite la redondance de données). D'anciens scripts peuvent encore utiliser ces variables. output_buffering à 4096 (par exemple) : plutôt que d'envoyer les données directement sur la sortie standard à chaque appel de echo ou print par exemple, conserver les données en mémoire avant de les envoyer permet de réduire le nombre d'accès à la sortie standard et de diminuer le nombre de paquets envoyés sur le réseau. Le gain de performance dépend toutefois également grandement de la configuration du serveur et du type de script PHP dont il s'agit. register_argc_argv à off : réduit le temps d'enregistrement des variables en début de script et l'utilisation mémoire (évite la redondance de données). magic_quotes_gpc à off : parce que toutes les données utilisateurs n'ont pas besoin d'être systématiquement échappés (en l'occurrence, cette option sert généralement pour les données enregistrées dans une base de données), désactiver cette option peut améliorer les performances. Cette option a un impact sur la sécurité ! De nombreux scripts ne relève que sur cette option pour assurer le bon formatage des données lors d'insertions dans une base de données notamment. variables_order à GPCS : ainsi, la variable d'environnement $_ENV n'est pas générée, ce qui réduit le temps d'enregistrement des variables en début de script et l'utilisation mémoire (il est possible alors d'accéder aux variables d'environnement avec la fonction PHP getenv()). Note : les réglages des paramètres décris ci avant font partie de la configuration recommandée de PHP et certaines options, comme register_long_arrays, sont déjà dépréciées. La désactivation de certaines options PHP ayant trait avec la sécurité peut améliorer considérablement les performances, en évitant certaines vérifications. Cependant, l'aspect sécurité prime sur l'aspect performances en ce qui concerne ces options, tout particulièrement chez pour des hébergements mutualisée. Les options principales sont : safe_mode, safe_mode_gid et open_basedir. Les options suivantes n'ont qu'un léger impact sur les performances : always_populate_raw_post_data à off : réduit le temps d'enregistrement des variables en début de script et l'utilisation mémoire en ne créant pas la variable $HTTP_RAW_POST_DATA lorsqu'elle ne contient pas de donnée. expose_php à off : réduit la quantité de donnée envoyée. Les options suivantes influes sur les performances lors de l'utilisation des sessions : session.auto_start à off : évite d'ouvrir une session à chaque script PHP (utiliser la fonction session_start() au besoin). session.use_trans_sid à off : évite à PHP le travail de réécriture des URLs de la page (si vous tenez vraiment à ce que l'ID de session soit transmis via l'URL, faite-le manuellement, au moins vous serez sûr(e) que l'ID sera bien transmis dans tous les cas de figure). Utilisation des expressions régulières Quand les utiliser ? Une règle d'or : ne jamais utiliser une expression régulière là où une autre fonction PHP peut faire l'affaire. Ainsi, préférer str_replace() à preg_replace(), explode() à preg_split(). Voici une liste de quelques fonctions pouvant parfois remplacer avantageusement les expressions régulières : • • • Remplacements : str_replace(), substr_replace(), strtr()... Recherches : strpos(), stripos(), strchr(), strrchr()... Comparaisons : strcmp(), strncasecmp()... ereg vs preg Les fonctions preg sont généralement plus performantes que les ereg pour des expressions régulières complexes (la différence reste cependant très faible lorsqu'il s'agit d'expressions régulières simples). De plus, les fonctions preg, en plus de supporter l'UTF-8, permettent d'effectuer des masques plus sophistiqués que leurs équivalents ereg. Méfiez-vous lorsque vous passer d'une fonction à l'autres, il y a des différences entres les syntaxes POSIX (pour les fonctions ereg) et les syntaxes compatibles Perl (pour les fonctions preg) qui peuvent parfois amener à des comportements différents et modifier le résultat final. Macros Il arrive couramment de vouloir remplacer certaines occurences d'une chaîne de caractère par des valeurs correspondantes. C'est le cas par exemple dans un système de cache où l'on souhaite gérer des variables dynamiques, comme une ID de session par exemple. Voici par exemple un tableau où les clefs correspondent aux occurences à remplacer par leur valeur respectives : $vars = array( '{var1}' => '{var2}' => '{var3}' => '{var4}' => '{var5}' => ); 'value1', 'value2', 'value3', 'value4', 'value5', Voici diverses implémentations courantes permettant d'atteindre notre but : $str = strtr($str, $vars); $str = str_replace(array_keys($vars), array_values($vars), $str); function replaceVar($sub) { global $vars; if (isset($vars[$sub[1]])) return $vars[$sub[1]]; else return '{'.$sub[1].'}'; } $str = preg_replace_callback('/\\{([^\\}]+)\\}/', 'replaceVar', $str); Cette méthode, utilisant la fonction preg_replace_callback() devrait en principe être la méthode la plus rapide, car il s'agit grossièrement d'un algorithme O(n) tandis que les méthodes précédentes sont O(n*m). Cependant, sa relative lenteur est dûe au coût de l'appel à la fonction replaceVar(). $vars = array( '/{var1}/' '/{var2}/' '/{var3}/' '/{var4}/' '/{var5}/' ); => => => => => 'value1', 'value2', 'value3', 'value4', 'value5', $str = preg_replace(array_keys($vars), array_values($vars), $str); Comparatif de performances Méthode Temps d'exécution strtr 0,42s str_replace 0,13s preg_replace_callback 0,19s preg_replace 0,26s Il est important de noter que la méthode utilisant preg_replace_callback devient la plus rapide lorsqu'il y a peu d'occurences à remplacer. La performance de l'algorithme compense alors les appels à la fonction de remplacement. On peut constater que la méthode utilisant la fonction strtr() est relativement peu performante. Ceci est dû au fait que cette fonction a en réalité un comportement un peu différent que str_replace() : elle cherche toujours en priorité la chaîne la plus longue et ne travaille pas sur des segments déjà modifiés. Le code ci-dessous illustre cette différence : $str = 'Le premier sera le dernier'; $replace = array( 'premier' => 'dernier', 'dernier' => 'premier' ); echo str_replace(array_keys($replace), array_values($replace), $str); /* Donne : "Le premier sera le premier" */ echo strtr($str, $replace); // Donne : "Le dernier sera le premier" Divers Ne pas générer systématiquement les variables : Il est inutile de générer une variable si vous n'êtes pas sûr(e) de vous en servir, notamment lorsqu'il s'agit de longs tableaux statiques. $LANG = array( // ... 'ro' => 'Roumain (Moldavie)', 'ro-mo' => 'Roumain (Moldavie)', 'ru' => 'Russe', 'ru-mo' => 'Russe (Moldavie)', 'sb' => 'Sorbian', 'sk' => 'Slovaque', 'sl' => 'Slovène', 'sq' => 'Albanais', // ... ); function getLang() { return array( // ... 'ro' => 'Roumain (Moldavie)', 'ro-mo' => 'Roumain (Moldavie)', 'ru' => 'Russe', 'ru-mo' => 'Russe (Moldavie)', 'sb' => 'Sorbian', 'sk' => 'Slovaque', 'sl' => 'Slovène', 'sq' => 'Albanais', // ... ); } substr($str, $i, 1) vs $str{$i} substr($str, $i, 1) à remplacer par $str{$i} substr($str, $i, 2) à remplacer par $str{$i}.$str{$i+1} substr($str, $i, 3) à remplacer par $str{$i}.$str{$i+1}.$str{$i+2} L'appel à la fonction substr() est plus lent que l'utilisation d'une structure du langage, cependant au-delà de 3 caractères, substr() se montre à nouveau plus rapide. echo (et print) echo "str".$var."str"; à remplacer par echo "str", $var, "str"; Avec des points, la chaîne est d'abord concaténée avant d'être envoyée sur la sortie, ce qui a tendance à diminuer très légèrement les performances (ne fonctionne qu'avec echo). On recommande souvent de privilégier echo à print pour de meilleures performances, parce que print retourne quelque chose (toujours la valeur 1) contrairement à echo. La différence de performance que j'ai pu constater est toutefois franchement négligeable... for, while for ($i = 0; $i < strlen($str); $i++) à remplacer par for ($i = 0, $length = strlen($str); $i < $length; $i++) Le fait d'intégrer strlen($str) dans la partie conditionnel n'est pas une bonne manière de programmer et diminue les performances, car la longueur de la chaîne (qui reste en principe fixe) est réévaluée à chaque passage de la boucle. strlen() n'est qu'un exemple de fonction possible. A ce propos, une meilleure structure est possible dans l'exemple ci-dessus : for ($i = 0, $length = strlen($str); $i < $length; $i++) à remplacer par for ($i = 0; isset($str{$i}); $i++) Si cette structure est plus rapide, c'est tout simplement qu'elle ne fait que reproduire l'algorithme de la fonction strlen(). Voici un équivalent de la première proposition (qui n'est pas un code PHP valide, mais une illustration) : for ($i = 0, for ($length = 0; isset($str{$length}); $length++); $i < $length; $i++). On comprend mieux maintenant pourquoi la deuxième proposition est plus performante, malgré le fait que l'on effectue l'opération isset(), qui est en fait également utilisé en interne (du moins l'équivalent en C) lors de l'appel à strlen(). unset Lorsque vous n'utiliser plus une variable, penser à la détruire, cela fera autant d'espace mémoire en moins (même si cela n'agit pas directement sur la vitesse d'exécution). Contrôler le type de donnée Vérifier qu'une chaîne représente un entier if ($str == (string) (int) $str) Cette structure est à préférer de tests du type if (preg_match("/^[0-9]+$/", $str)) ou if (ereg("^[0-9]+$", $str)). Vérifier qu'une chaîne représente un nombre if ($str == (string) (float) $str) Vérifier qu'une chaîne représente un booléen if ($str == (string) (bool) $str) Ecrit par Olivier BICHLER, le 15/02/2007 22:14:24