Tout récemment, j'ai encore entendu dire que les gens de PHP parlent encore de guillemets simples et de guillemets doubles et que l'utilisation de guillemets simples n'est qu'une micro-optimisation, mais si vous vous habituez à utiliser des guillemets simples tout le temps, vous économiserez beaucoup de CPU cycle !
"Tout a déjà été dit, mais pas encore par tout le monde" – Karl Valentin
C'est dans cet esprit que j'écris un article sur le même sujet que Nikita Popov avait déjà écrit il y a 12 ans (si vous lisez son article, vous pouvez arrêter de lire ici).
PHP effectue une interpolation de chaîne, dans laquelle il recherche l'utilisation de variables dans une chaîne et les remplace par la valeur de la variable utilisée :
$juice = "apple"; echo "They drank some $juice juice."; // will output: They drank some apple juice.
Cette fonctionnalité est limitée aux chaînes entre guillemets doubles et heredoc. L'utilisation de guillemets simples (ou nowdoc) donnera un résultat différent :
$juice = "apple"; echo 'They drank some $juice juice.'; // will output: They drank some $juice juice.
Regardez ça : PHP ne recherchera pas de variables dans cette chaîne entre guillemets simples. Nous pourrions donc simplement commencer à utiliser des guillemets simples partout. Alors les gens ont commencé à suggérer des changements comme celui-ci.
- $juice = "apple"; $juice = 'apple';
.. parce que ce sera plus rapide et cela économiserait beaucoup de cycles CPU à chaque exécution de ce code car PHP ne recherche pas de variables dans des chaînes entre guillemets simples (qui sont de toute façon inexistantes dans l'exemple) et tout le monde est content, affaire classée.
Évidemment, il y a une différence entre l'utilisation de guillemets simples et de guillemets doubles, mais pour comprendre ce qui se passe, nous devons creuser un peu plus.
Même si PHP est un langage interprété, il utilise une étape de compilation dans laquelle certaines parties jouent ensemble pour obtenir quelque chose que la machine virtuelle peut réellement exécuter, à savoir les opcodes. Alors, comment passer du code source PHP aux opcodes ?
Lexer analyse le fichier de code source et le décompose en jetons. Un exemple simple de ce que cela signifie peut être trouvé dans la documentation de la fonction token_get_all(). Un code source PHP de juste
T_OPEN_TAG (Nous pouvons voir cela en action et jouer avec dans cet extrait de 3v4l.org.
L'analyseur
L'analyseur prend ces jetons et génère à partir d'eux un arbre de syntaxe abstrait. Une représentation AST de l'exemple ci-dessus ressemble à ceci lorsqu'elle est représentée sous forme de JSON :
{ "data": [ { "nodeType": "Stmt_Echo", "attributes": { "startLine": 1, "startTokenPos": 1, "startFilePos": 6, "endLine": 1, "endTokenPos": 4, "endFilePos": 13 }, "exprs": [ { "nodeType": "Scalar_String", "attributes": { "startLine": 1, "startTokenPos": 3, "startFilePos": 11, "endLine": 1, "endTokenPos": 3, "endFilePos": 12, "kind": 2, "rawValue": "\"\"" }, "value": "" } ] } ] }Au cas où vous voudriez également jouer avec cela et voir à quoi ressemble l'AST pour d'autres codes, j'ai trouvé https://phpast.com/ de Ryan Chandler et https://php-ast-viewer.com/ qui les deux vous montrent l'AST d'un morceau de code PHP donné.
Le compilateur
Le compilateur prend l'AST et crée des opcodes. Les opcodes sont les choses que la machine virtuelle exécute, c'est aussi ce qui sera stocké dans l'OPcache si vous avez cette configuration et activée (ce que je recommande fortement).
Pour afficher les opcodes, nous avons plusieurs options (peut-être plus, mais je connais ces trois ):
- utilisez l'extension de dumper logique vulcan. Il est également intégré à 3v4l.org
- utilisez phpdbg -p script.php pour vider les opcodes
- ou utilisez le paramètre INI opcache.opt_debug_level pour OPcache pour lui faire imprimer les opcodes
- une valeur de 0x10000 génère les opcodes avant l'optimisation
- une valeur de 0x20000 génère des opcodes après l'optimisation
$ echo ' foo.php $ php -dopcache.opt_debug_level=0x10000 foo.php $_main: ... 0000 ECHO string("") 0001 RETURN int(1)Hypothèse
Pour en revenir à l'idée initiale d'économiser les cycles du processeur lors de l'utilisation de guillemets simples plutôt que de guillemets doubles, je pense que nous sommes tous d'accord sur le fait que cela ne serait vrai que si PHP évaluait ces chaînes au moment de l'exécution pour chaque requête.
Que se passe-t-il au moment de l'exécution ?
Voyons donc quels opcodes PHP crée pour les deux versions différentes.
Guillemets doubles :
0000 ECHO string("apple") 0001 RETURN int(1)contre. guillemets simples :
0000 ECHO string("apple") 0001 RETURN int(1)Hé, attends, quelque chose de bizarre s'est produit. Cela a l'air identique ! Où est passée ma micro-optimisation ?
Eh bien peut-être, juste peut-être que l'implémentation du gestionnaire d'opcode ECHO analyse la chaîne donnée, bien qu'il n'y ait pas de marqueur ou autre chose qui lui dit de le faire... hmm ?
Essayons une approche différente et voyons ce que fait le lexer dans ces deux cas :
Guillemets doubles :
T_OPEN_TAG (contre. guillemets simples :
Line 1: T_OPEN_TAG (Les jetons font toujours la distinction entre les guillemets doubles et simples, mais vérifier l'AST nous donnera un résultat identique dans les deux cas - la seule différence est la valeur rawValue dans les attributs du nœud Scalar_String, qui a toujours les guillemets simples/doubles, mais la valeur utilise des guillemets doubles dans les deux cas.
Nouvelle hypothèse
Se pourrait-il que l'interpolation de chaîne soit réellement effectuée au moment de la compilation ?
Vérifions avec un exemple un peu plus "sophistiqué" :
Les jetons de ce fichier sont :
T_OPEN_TAG (Regardez les deux derniers jetons ! L'interpolation de chaîne est gérée dans le lexer et, en tant que telle, est une opération de compilation et n'a rien à voir avec l'exécution.
Pour être complet, regardons les opcodes générés par ceci (après optimisation, en utilisant 0x20000) :
0000 ASSIGN CV0($juice) string("apple") 0001 T2 = FAST_CONCAT string("juice: ") CV0($juice) 0002 ECHO T2 0003 RETURN int(1)C'est un opcode différent de celui que nous avions dans notre simple
Allez droit au but : dois-je concaténer ou interpoler ?
Jetons un coup d'œil à ces trois versions différentes :
Le premier opcode attribue la chaîne "apple" à la variable $juice :
0000 ASSIGN CV0($juice) string("apple")
La première version (interpolation de chaînes) utilise une corde comme structure de données sous-jacente, qui est optimisée pour effectuer le moins de copies de chaînes possible.
0001 T2 = ROPE_INIT 4 string("juice: ") 0002 T2 = ROPE_ADD 1 T2 CV0($juice) 0003 T2 = ROPE_ADD 2 T2 string(" ") 0004 T1 = ROPE_END 3 T2 CV0($juice) 0005 ECHO T1
La deuxième version est la plus efficace en termes de mémoire car elle ne crée pas de représentation sous forme de chaîne intermédiaire. Au lieu de cela, il effectue plusieurs appels à ECHO, ce qui est un appel bloquant du point de vue des E/S, donc selon votre cas d'utilisation, cela peut être un inconvénient.
0006 ECHO string("juice: ") 0007 ECHO CV0($juice) 0008 ECHO string(" ") 0009 ECHO CV0($juice)
La troisième version utilise CONCAT/FAST_CONCAT pour créer une représentation de chaîne intermédiaire et, en tant que telle, peut utiliser plus de mémoire que la version corde.
0010 T1 = CONCAT string("juice: ") CV0($juice) 0011 T2 = FAST_CONCAT T1 string(" ") 0012 T1 = CONCAT T2 CV0($juice) 0013 ECHO T1
Alors... quelle est la bonne chose à faire ici et pourquoi s'agit-il d'une interpolation de chaîne ?
L'interpolation de chaîne utilise soit un FAST_CONCAT dans le cas de echo "juice: $juice" ; ou des opcodes ROPE_* hautement optimisés dans le cas de echo "juice: $juice $juice";, mais le plus important, il communique clairement l'intention et rien de tout cela n'a été un goulot d'étranglement dans aucune des applications PHP avec lesquelles j'ai travaillé jusqu'à présent, donc rien de tout cela n'a d'importance.
L'interpolation de chaînes est une opération au moment de la compilation. Certes, sans OPcache, le lexer devra vérifier les variables utilisées dans les chaînes entre guillemets doubles à chaque requête, même s'il n'y en a pas, ce qui réduira les cycles du processeur, mais honnêtement : le problème ne vient pas des chaînes entre guillemets doubles, mais de l'utilisation d'OPcache !
Cependant, il y a une mise en garde : PHP jusqu'à 4 (et je crois même y compris 5.0 et peut-être même 5.1, je ne sais pas) effectuait une interpolation de chaîne au moment de l'exécution, donc en utilisant ces versions... hmm, je suppose que si si quelqu'un utilise encore PHP 5, la même chose que ci-dessus s'applique : le problème ne vient pas des chaînes entre guillemets doubles, mais de l'utilisation d'une version obsolète de PHP.
Mettez à jour vers la dernière version de PHP, activez OPcache et vivez heureux pour toujours !
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3