55 votes

isset() vs strlen() - un calcul rapide/clair de la longueur des chaînes de caractères

Je suis tombé sur ce code...

if(isset($string[255])) {
    // too long
}

isset() est entre 6 et 40 fois plus rapide que

if(strlen($string) > 255) {
    // too long
}

Le seul inconvénient de isset() est que le code n'est pas clair - on ne peut pas dire tout de suite ce qui est fait (voir la réponse de pekka). Nous pouvons envelopper isset() dans une fonction, par exemple strlt($string,255), mais nous perdons alors les avantages de isset() en termes de vitesse.

Comment utiliser la fonction isset(), plus rapide, tout en conservant la lisibilité du code ?

EDIT : test pour montrer la vitesse http://codepad.org/ztYF0bE3

strlen() over 1000000 iterations 7.5193998813629
isset() over 1000000 iterations 0.29940009117126

EDIT2 : voici pourquoi isset() est plus rapide

$string = 'abcdefg';
var_dump($string[2]);
Output: string(1) “c”

$string = 'abcdefg';
if (isset($string[7])){
     echo $string[7].' found!';
  }else{
     echo 'No character found at position 7!';
}

Cette méthode est plus rapide que l'utilisation de strlen() car " l'appel à une fonction est plus coûteux que l'utilisation d'une construction du langage". http://www.phpreferencebook.com/tips/use-isset-instead-of-strlen/

EDIT3 : On m'a toujours appris à m'intéresser à la mirco-optimisation. Probablement parce que j'ai été enseigné à une époque où les ressources sur les ordinateurs étaient minuscules. Je suis ouvert à l'idée que cela puisse ne pas être important, il y a quelques bons arguments contre cela dans les réponses. J'ai commencé une nouvelle question pour explorer ce sujet... https://stackoverflow.com/questions/6983208/is-micro-optimisation-important-when-coding

53voto

Roel Points 9657

J'ai donc effectué les tests car j'avais du mal à croire que la méthode isset() était plus rapide, mais c'est le cas, et de façon considérable. La méthode isset() est systématiquement 6 fois plus rapide.

J'ai essayé avec des chaînes de différentes tailles et en exécutant un nombre variable d'itérations ; les ratios restent les mêmes, ainsi que la longueur totale de l'exécution d'ailleurs (pour les chaînes de différentes tailles), parce que isset() et strlen() sont tous deux O(1) (ce qui est logique - isset a seulement besoin de faire une recherche dans un tableau C, et strlen() renvoie seulement le compte de taille qui est conservé pour la chaîne).

J'ai regardé dans les sources de php, et je pense que je comprends à peu près pourquoi. isset(), parce que ce n'est pas une fonction mais une construction du langage, a son propre opcode dans la Zend VM. Par conséquent, elle n'a pas besoin d'être recherchée dans la table des fonctions et elle peut faire un parsing plus spécialisé des paramètres. Le code est dans zend_builtin_functions.c pour strlen() et zend_compile.c pour isset(), pour ceux qui sont intéressés.

Pour en revenir à la question initiale, je ne vois aucun problème avec la méthode isset() d'un point de vue technique, mais je pense qu'elle est plus difficile à lire pour les personnes qui ne sont pas habituées à cet idiome. De plus, la méthode isset() sera constante en temps, alors que la méthode strlen() sera O(n) si l'on fait varier le nombre de fonctions intégrées à PHP. En d'autres termes, si vous construisez PHP et compilez statiquement de nombreuses fonctions, tous les appels de fonctions (y compris strlen()) seront plus lents ; mais isset() sera constant. Cependant, cette différence sera négligeable en pratique ; je ne sais pas non plus combien de tables de pointeurs de fonctions sont maintenues, donc si les fonctions définies par l'utilisateur ont aussi une influence. Je crois me souvenir qu'elles se trouvent dans une table différente et ne sont donc pas pertinentes dans ce cas, mais cela fait un moment que je n'ai pas vraiment travaillé sur ce sujet.

Pour le reste, je ne vois pas d'inconvénients à la méthode isset(). Je ne connais pas d'autres façons d'obtenir la longueur d'une chaîne de caractères, si l'on ne considère pas les méthodes délibérément alambiquées comme explode+count et autres.

Enfin, j'ai également testé votre suggestion ci-dessus d'intégrer isset() dans une fonction. C'est plus lent que la méthode strlen() car il faut un autre appel de fonction, et donc une autre consultation de la table de hachage. L'overhead du paramètre supplémentaire (pour la taille à vérifier) est négligeable, tout comme la copie de la chaîne lorsqu'elle n'est pas passée par référence.

19voto

Pekka 웃 Points 249607

Toute différence de vitesse dans ce domaine est absolument sans conséquence. Elle sera de quelques millisecondes au mieux.

Utilisez le style le plus lisible pour vous et pour toute autre personne travaillant sur le code. Personnellement, je voterais fortement pour le deuxième exemple car, contrairement au premier, il rend l'intention (vérifier la longueur d'une chaîne) absolument claire.

12voto

Boris Yankov Points 1181

Votre code est incomplet.

Tiens, je l'ai réparé pour toi :

if(isset($string[255])) {
    // something taking 1 millisecond
}

vs

if(strlen($string) > 255) {
    // something taking 1 millisecond
}

Maintenant, vous n'avez pas une boucle vide, mais une boucle réaliste. Considérons qu'il faut 1 milliseconde pour faire quelque chose.

Un processeur moderne peut faire beaucoup de choses en une milliseconde - c'est un fait. Mais des choses comme un accès aléatoire à un disque dur ou une requête à une base de données prennent plusieurs millisecondes - c'est également un scénario réaliste.

Maintenant, calculons à nouveau les timings :

realistic routine + strlen() over 1000000 iterations 1007.5193998813629
realistic routine + isset() over 1000000 iterations 1000.29940009117126

Vous voyez la différence ?

5voto

NikiC Points 47270

Tout d'abord, je tiens à souligner une réponse de Artefacto expliquer pourquoi les appels de fonction ont un coût supplémentaire par rapport aux constructions du langage.

Deuxièmement, je tiens à vous faire savoir que XDebug diminue considérablement les performances des appels de fonction. Si vous utilisez XDebug, vous risquez donc d'obtenir des chiffres alambiqués. Référence (Deuxième section de la question). Ainsi, en production (où, espérons-le, vous n'avez pas installé XDebug), la différence est encore plus faible. Elle passe de 6x à 2x.

Troisièmement, vous devez savoir que, même s'il existe une différence mesurable, cette différence n'apparaît que si ce code tourne en boucle serrée avec des millions d'itérations. Dans une application web normale, la différence ne sera pas mesurable, elle sera noyée dans le bruit de la variance.

Quatrièmement, veuillez noter qu'aujourd'hui, le temps de développement est beaucoup plus coûteux que la charge du serveur. Un développeur qui passe ne serait-ce qu'une demi-seconde de plus à comprendre ce que fait le code isset est bien plus cher que l'économie de charge CPU. De plus, la charge du serveur peut être bien mieux économisée en appliquant des optimisations qui font réellement la différence (comme la mise en cache).

2voto

Ismael Miguel Points 21

C'est le dernier test :

function benchmark_function($fn,$args=null)
{
    if(!function_exists($fn))
    {
        trigger_error("Call to undefined function $fn()",E_USER_ERROR);
    }

    $t = microtime(true);

    $r = call_user_func_array($fn,$args);

    return array("time"=>(microtime(true)-$t),"returned"=>$r,"fn"=>$fn);
}

function get_len_loop($s)
{
    while($s[$i++]){}
    return $i-1;
}
echo var_dump(benchmark_function("strlen","kejhkhfkewkfhkwjfjrw"))."<br>";
echo var_dump(benchmark_function("get_len_loop","kejhkhfkewkfhkwjfjrw"));

Résultats retournés :

RUN 1 :

array(3) { ["time"]=> float(2.1457672119141E-6) ["returned"]=> int(20) ["fn"]=> string(6) "strlen" } array(3) { ["time"]=> float(1.1920928955078E-5) ["returned"]=> int(20) ["fn"]=> string(12) "get_len_loop" }

RUN 2 :

array(3) { ["time"]=> float(4.0531158447266E-6) ["returned"]=> int(20) ["fn"]=> string(6) "strlen" } array(3) { ["time"]=> float(1.5020370483398E-5) ["returned"]=> int(20) ["fn"]=> string(12) "get_len_loop" }

RUN 3 :

array(3) { ["time"]=> float(4.0531158447266E-6) ["returned"]=> int(20) ["fn"]=> string(6) "strlen" } array(3) { ["time"]=> float(1.2874603271484E-5) ["returned"]=> int(20) ["fn"]=> string(12) "get_len_loop" }

RUN 4 :

array(3) { ["time"]=> float(3.0994415283203E-6) ["returned"]=> int(20) ["fn"]=> string(6) "strlen" } array(3) { ["time"]=> float(1.3828277587891E-5) ["returned"]=> int(20) ["fn"]=> string(12) "get_len_loop" }

RUN 5 :

array(3) { ["time"]=> float(5.0067901611328E-6) ["returned"]=> int(20) ["fn"]=> string(6) "strlen" } array(3) { ["time"]=> float(1.4066696166992E-5) ["returned"]=> int(20) ["fn"]=> string(12) "get_len_loop" }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X