Instancier un objet dynamiquement sans eval()

5 mars 2008 par Olivier Mansour

eval() est décidément un gouffre à performance !

Voici le script que j’ai utilisé pour comparer les performances de l’instanciation d’un objet avec eval ou avec new. (j’utilise php 5.2.5).

<?php
 
$nb_iteration = 400000;
 
class toto {
 public $raoul;
}
 
$class_name = 'toto';
 
$start_time = microtime(true);
for ($i=0; $i <= $nb_iteration; $i++) {
 eval ('$objet = new toto();');
}
$end_time = microtime(true);
 
echo 'time taken (with eval)      : '.($end_time-$start_time).' s'."\n";
 
$start_time = microtime(true);
for ($i=0; $i <= $nb_iteration; $i++) {
 $objet = new $class_name();
}
$end_time = microtime(true);
 
echo 'time taken (no eval)        : '.($end_time-$start_time).' s'."\n";

et voici les résultats :

$ php test_eval.php
time taken (with eval)      : 18.955335140228 s
time taken (no eval)        : 3.5720331668854 s

Parlant non ?

Pour soutenir ce site, n'hésitez pas à cliquer sur un de ces liens :

  1. Par Hugo le 5 mars 2008 | Répondre

    Plus que parlant même !

  2. Par Antoine le 5 mars 2008 | Répondre

    Salut,

    il manque une balise de fermeture à ton script. Aussi, il est sans doute utile de préciser que faire new $class_name() ne prends pas plus de temps que de faire new toto().

    La fonction call_user_func est un bon compromis pour ce genre de chose. Par exemple, sur mon serveur :
    time taken (with eval) : 2.98381304741 s
    time taken (no eval) : 0.30176615715 s
    time taken (call_user_func) : 0.819909095764 s

    Antoine

  3. Par Laurentj le 5 mars 2008 | Répondre

    Oui c’est totalement logique, quand on sait que l’évaluation d’un script PHP se fait en deux phases : transformation du code source en bytecode, puis évaluation du bytecode.

    Avec eval, on a à chaque itération les deux phases. Tandis qu’en faisant un new direct, on a que la deuxième phase, l’instruction dans la boucle ayant déjà été transformé en bytecode avant l’execution globale du bytecode du script.

  4. Par Chris le 5 mars 2008 | Répondre

    @Antoine : la balise de fermeture n’est pas obligatoire. C’est même mieux d’ailleurs de ne pas l’utiliser pour éviter un retour chariot intempestif en fin de fichier, qui serait envoyé directement sur la sortie standard. Symfony a adopté cette pratique par exemple.

  5. Par Gérald le 5 mars 2008 | Répondre

    Pour compléter les tests, voici ce que ça donne avec l’api Reflection

    $start_time = microtime(true);
    for ($i=0; $i newInstance ();
    }
    $end_time = microtime(true);

    echo ‘time taken (new Reflection) : ‘.($end_time-$start_time).’ s’.”\n”;

    $start_time = microtime(true);
    $reflection = new ReflectionClass ($class_name);
    for ($i=0; $i newInstance ();
    }
    $end_time = microtime(true);

    echo ‘time taken (keeped Reflection) : ‘.($end_time-$start_time).’ s’.”\n”;

    time taken (with eval) : 9.55426383018 s
    time taken (no eval) : 0.662239074707 s
    time taken (new Reflection) : 4.30600905418 s
    time taken (keeped Reflection object) : 1.75258493423 s

  6. Par Antoine le 5 mars 2008 | Répondre

    @Chris, je n’ai pas de retour chariot si je termine mes scripts. Je ne dirais pas que c’est mieux de ne pas fermer ses scripts par contre…

  7. Par Antoine le 5 mars 2008 | Répondre

    @Gerald, pris de cours, je terminais mes tests :)

  8. Par Gérald le 5 mars 2008 | Répondre

    Tient, le code est mal passé dans les commentaires….. un strip tags qui doit traîner :-)

    Pour résumer, dans le premier cas j’effectue un new ReflectionClass ($class_name) à chaque passage dans le foreach, en plus de l’appel à la méthode newInstance (). Dans le deuxième cas, je ne fais qu’appeler la méthode newInstance () en ayant au préalable instancié la ReflectionClass.

    Dans tous les cas donc, new $class_name est plus rapide.

  9. Par Olivier Mansour le 6 mars 2008 | Répondre

    Merci pour vos commentaires et les compléments de test.

    @LaurentJ, je suis tout de même étonné du rapport entre les deux temps de mon script, je m’attendais à un rapport 2, pas 6 !

  10. Par Martin le 6 mars 2008 | Répondre

    En même temps, il faut bien garder à l’esprit que l’on est sur une boucle de 400 000 itérations, et donc que toutes les solutions sont envisageable dans un cas bien précis. On ne peut pas toujours utiliser la méthode la plus rapide, et même la moins rapide utilisée avec parcimonie est tout à fait correcte.

  1. 1 Trackback(s)

  2. mar 6, 2008: Laurent Deséchalliers : “Tech”Blog » [Veille>Dev] PHP : Bench de la function eval()

Commentaires

RSS des commentaires pour ce post