OptimisationDerrière ces deux noms barbares, les POSIX et les PCRE, se cache deux librairies d’expressions rationnelles. Pour être plus parlant, lorsque vous utilisez les fonctions « ereg » vous utilisez la librairie POSIX et lorsque vous utilisez les fonctions « preg » vous utilisez la librairie PCRE. A noter tout de même que les deux librairies (et donc les fonctions associées) permettent de faire exactement les mêmes choses.

Comme vous pouvez le remarquer sur la documentation officielle de PHP vous trouverez ce genre de mention lorsque vous consultez la documentation des fonctions ereg :

Note: preg_match(), qui utilise la syntaxe des expressions rationnelles compatibles PERL, est une alternative plus rapide de ereg()

Notez aussi que vous avez désormais cette mention qui apparait :

Cette fonction est OBSOLETE depuis PHP 5.3.0 et a été SUPPRIMEE depuis PHP 6.0.0. Nous vous encourageons vivement à ne plus l’utiliser.

Alors pourquoi l’utilisation des POSIX est déconseillée, pourquoi depuis PHP 5.3 elles sont devenues obsolètes et enfin pourquoi elles vont être supprimée dans PHP 6 ? La réponse dans la suite de cette article…

Pour faire ce test de performance, on va utiliser le même protocole de test que dans l’article sur la concaténation. Nous allons utiliser sur ce petit test la validation d’une adresse email. Nous étudierons deux cas, le cas ou l’adresse email est bonne et le cas ou elle ne l’est pas. Nous baserons nos tests sur 100 000 itérations pour révéler clairement les différences de performance. Les regex dans les tests seront tirés du très bon site Expreg.

  • Cas avec une adresse email correcte

Voici le code que nous allons utiliser pour ce premier test, avec la fonction ereg (POSIX):

<?php
include('Benchmark.class.php');
 
$var = 'info@e-ur-ope.a.n-cards.com';
$perf = new Benchmark();
 
for($i=0;$i<100000;$i++){
    ereg("^[[:alnum:]]([-_.]?[[:alnum:]])*@[[:alnum:]]([-.]?[[:alnum:]])*\.([a-z]{2,4})$",$var);
}
 
$perf->stop();
echo $perf->getStats().' secondes';
?>

Et voici celui pour preg_match (PCRE):

<?php
include('Benchmark.class.php');
 
$var = 'info@e-ur-ope.a.n-cards.com';
$perf = new Benchmark();
 
for($i=0;$i<100000;$i++){
    preg_match('`^[[:alnum:]]([-_.]?[[:alnum:]])*@[[:alnum:]]([-.]?[[:alnum:]])*\.([a-z]{2,4})$`',$var);
}
 
$perf->stop();
echo $perf->getStats().' secondes';
?>

Voici les résultats dans un graphique :

On se rend compte que la fonction ereg est ainsi 38% plus lente que la fonction preg_match sur ce simple test, un écart de performance qui est tout de même assez conséquent et qui commence à confirmer le choix de la team PHP de vouloir supprimer les fonctions POSIX…

  • Cas avec une adresse email incorrecte

Nous allons maintenant faire les mêmes tests avec une chaîne invalide qui est :

$var = 'info@e-ur-ope.a.n-cards .com';

Voici les résultats :

On constate ainsi plusieurs choses avec ce graphique, la première c’est que pour l’une ou l’autre des fonctions le temps de réponse est plus court que lors du premier test, ce qui est normal par rapport au fonctionnement d’une expression régulière. Deuxième observation, on  remarque que l’écart de performance est pour ainsi dire identique (37%) que lors du premier test, mais est ce que cet écart est toujours le même avec une chaîne plus longue ? C’est ce que nous allons voir  :

$var = 'une_tr_longu_adresse_email_qui_est_interminable_info@e-ur-ope.a.n-cards .com';

Et voici le résultat :

Vous le voyez vous même… L’écart de performance commence sérieusement à grimper, la fonction ereg est ainsi 73% plus lente que la fonction preg_match, échec et mat !

En conclusion de ce bref test (est-il utile d’en faire plus ?) on remarque bien l’écart important de performance entre les POSIX et les PCRE qui devient d’autant plus important lorsque l’on travaille sur de longues chaînes de caractères mais aussi en fonction de la complexité du masque. L’utilisation des POSIX est donc à bannir de vos scripts et personne ne regrettera le choix de PHP de… L’abandonner !

Pour aller plus loin les expressions régulières sont des fonctions lourdes et je ne saurais trop vous conseiller de ne les utiliser que lorsque vous en avez réellement besoin (cf le bonus ci-dessous).

Bonus: Pour aller plus loin…

Imaginons que vous voulez détecter la présence d’un mot dans une chaîne de caractéres, je remarque souvent que beaucoup utilisent une expression régulière plutôt que strpos. J’ai toujours été partisan d’utiliser strpos dans ce cas de figure car je pense qu’une fonction native dédiée à ce que l’on veut est plus rapide… Voici donc un petit test avec preg_match :

<?php
include('Benchmark.class.php');
 
$var = 'Check this string for finding a GoogleBot !';
$perf = new Benchmark();
 
for($i=0;$i<300000;$i++){
    if(preg_match('`^.*GoogleBot.*$`',$var)){
        // Action
    }
}
 
$perf->stop();
echo $perf->getStats().' secondes';
?>

Et le même avec strpos :

<?php
include('Benchmark.class.php');
 
$var = 'Check this string for finding a GoogleBot !';
$perf = new Benchmark();
 
for($i=0;$i<300000;$i++){
    if(strpos($var,'GoogleBot') !== false){
        // Action
    }
}
 
$perf->stop();
echo $perf->getStats().' secondes';
?>

Ce test consiste simplement à trouver le mot « GoogleBot » dans une chaîne de caractères. Et voici le résultat :

On remarque ici que l’expression régulière est 11% plus lente que la fonction strpos, tout en considérant que plus la chaîne à analyser est longue, plus l’écart sera important. Il est donc préférable dans ce cas d’utiliser strpos plutôt qu’une expression régulière.

Dans le même esprit PHP a introduit récemment les fonctions filter, il est ainsi possible de tester la validité d’une adresse email avec celles-ci. Mais qu’en est il question performance ? Nous allons pour ce faire reprendre les résultats lors de notre premier test avec preg_match et nous allons les confronter avec ce test :

<?php
include('Benchmark.class.php');
 
$var = 'info@e-ur-ope.a.n-cards.com';
$perf = new Benchmark();
 
for($i=0;$i<100000;$i++){
    filter_var($var, FILTER_VALIDATE_EMAIL);
}
 
$perf->stop();
echo $perf->getStats().' secondes';
?>

Voici les résultats :

On se rend malheureusement compte que la fonction filter_var est ainsi environ 10% plus lente que la fonction preg_match… Il y a encore du travail de ce coté, et pour ne pas arranger les choses le filtre FILTER_VALIDATE_EMAIL comporte de nombreux bugs… je vous déconseille donc de l’utiliser à l’heure actuelle et de préférer les expressions régulières.

<?php
include(‘Benchmark.class.php’);

$var = ‘Check this string for finding a GoogleBot !’;
$perf = new Benchmark();

for($i=0;$i<100000;$i++){
if(preg_match(‘`^.*GoogleBot.*$`’,$var)){
// Action
}
}

$perf->stop();
echo $perf->getStats().’ secondes’;
?>