Préface
Dans n'importe quelle langue, les fonctions sont les éléments de base les plus élémentaires. Quelles sont les caractéristiques des fonctions PHP ? Comment l’appel de fonction est-il implémenté ? Comment sont les performances des fonctions PHP ? Des suggestions d’utilisation ? Cet article tentera de répondre à ces questions en analysant les principes et en les combinant avec des tests de performances réels, afin de mieux écrire des programmes PHP tout en comprenant leur implémentation. Parallèlement, certaines fonctions PHP courantes seront introduites.
Classification des fonctions PHP
En PHP, si elles sont divisées horizontalement, les fonctions sont divisées en deux catégories : fonction utilisateur (fonction intégrée) et la fonction interne. Les premières sont des fonctions et méthodes personnalisées par les utilisateurs du programme, et les secondes sont diverses fonctions de bibliothèque fournies par PHP lui-même (telles que sprintf, array_push, etc.). Les utilisateurs peuvent également écrire des fonctions de bibliothèque via des méthodes d'extension, qui seront présentées plus tard. Pour la fonction utilisateur, elle peut être subdivisée en fonction (fonction) et méthode (méthode de classe). Dans cet article, ces trois fonctions seront analysées et testées respectivement.
Tutoriels recommandés : Tutoriel vidéo PHP
Implémentation des fonctions php
Comment une fonction php est-elle finalement exécutée ?
Pour répondre à cette question, jetons d’abord un coup d’œil au processus d’exécution du code PHP.
Comme vous pouvez le voir sur l'image, PHP implémente un processus d'exécution de langage dynamique typique : après avoir obtenu un morceau de code, après avoir traversé des étapes telles que l'analyse lexicale et l'analyse syntaxique , le code source du programme sera traduit en instructions (opcodes), puis la machine virtuelle ZEND exécutera ces instructions en séquence pour terminer l'opération. Php lui-même est implémenté en C, donc les fonctions finalement appelées sont toutes des fonctions C. En fait, on peut considérer PHP comme un logiciel développé en C.
Il n'est pas difficile de voir à partir de la description ci-dessus que l'exécution de fonctions en PHP est également traduite en opcodes pour l'appel. Chaque appel de fonction exécute en fait une ou plusieurs instructions.
Pour chaque fonction, zend la décrit à travers la structure de données suivante
typedef union _zend_function { zend_uchar type; /* MUST be the first element of this struct! */ struct { zend_uchar type; /* never used */ char *function_name; zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint required_num_args; zend_arg_info *arg_info; zend_bool pass_rest_by_reference; unsigned char return_reference; } common; zend_op_array op_array; zend_internal_function internal_function; } zend_function; typedef struct _zend_function_state { HashTable *function_symbol_table; zend_function *function; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; } zend_function_state;
Où type indique le type de fonction : fonction utilisateur, fonction intégrée, fonction surchargée. Common contient les informations de base de la fonction, y compris le nom de la fonction, les informations sur les paramètres, les indicateurs de fonction (fonctions ordinaires, méthodes statiques, méthodes abstraites), etc. De plus, pour les fonctions utilisateur, il existe également une table de symboles de fonction qui enregistre les variables internes, etc., qui seront décrites en détail ultérieurement. Zend maintient une function_table globale, qui est une grande table de hachage. Lorsqu'une fonction est appelée, la fonction zend_function correspondante sera d'abord trouvée dans la table en fonction du nom de la fonction. Lors d'un appel de fonction, la machine virtuelle détermine la méthode d'appel en fonction du type. Différents types de fonctions ont des principes d'exécution différents.
Fonctions intégrées
Les fonctions intégrées sont essentiellement de véritables fonctions C. Pour chaque fonction intégrée, PHP l'étendra après. compilation finale Devenez une fonction nommée zif_xxxx, comme notre sprintf commun, qui correspond à la couche inférieure est zif_sprintf. Lorsque Zend est en cours d'exécution, s'il trouve une fonction intégrée, il effectue simplement une opération de transfert.
Zend fournit une série d'API pour les appels, notamment l'acquisition de paramètres, les opérations sur les tableaux, l'allocation de mémoire, etc. Les paramètres de la fonction intégrée sont obtenus via la méthode zend_parse_parameters. Pour les paramètres tels que les tableaux et les chaînes, zend implémente une copie superficielle, cette efficacité est donc très élevée. On peut dire que pour les fonctions PHP intégrées, leur efficacité est presque la même que celle des fonctions C correspondantes, avec le seul appel de transfert supplémentaire.
Les fonctions intégrées sont chargées dynamiquement en PHP grâce à cela. Les utilisateurs peuvent également écrire en fonction de leurs besoins, ce que nous appelons souvent des extensions. ZEND fournit une série d'API pour l'utilisation d'extensions
Fonctions utilisateur
Par rapport aux fonctions intégrées, les fonctions définies par l'utilisateur implémentées via PHP ont des processus d'exécution et des principes de mise en œuvre complètement différents. Comme mentionné ci-dessus, nous savons que le code PHP est traduit en opcodes pour l'exécution, et les fonctions utilisateur ne font pas exception. En fait, chaque fonction correspond à un ensemble d'opcodes, et cet ensemble d'instructions est enregistré dans zend_function. Par conséquent, l’appel à la fonction utilisateur correspond finalement à l’exécution d’un ensemble d’opcodes.
Enregistrez les variables locales et implémentez la récursion
Nous savons que la récursivité des fonctions s'effectue via la pile. En PHP, une méthode similaire est utilisée pour y parvenir. Zend attribue une table de symboles actifs (active_sym_table) à chaque fonction PHP pour enregistrer l'état de toutes les variables locales dans la fonction actuelle. Toutes les tables de symboles sont conservées sous la forme d'une pile. Chaque fois qu'une fonction est appelée, une nouvelle table de symboles est allouée et placée sur la pile. À la fin de l'appel, la table des symboles actuelle est retirée de la pile. Cela permet la préservation de l’état et la récursivité.
Pour la maintenance de la pile, zend l'a optimisé ici. Pré-allouer un tableau statique de longueur N pour simuler la pile. Cette méthode de simulation de structures de données dynamiques via des tableaux statiques est également souvent utilisée dans nos propres programmes. Cette méthode évite l'allocation de mémoire provoquée par chaque appel. ZEND nettoie simplement les données de la table de symboles en haut de la pile actuelle à la fin de l'appel de fonction.
Parce que la longueur du tableau statique est N, une fois que le niveau d'appel de fonction dépasse N, le programme ne provoquera pas de débordement de pile. Dans ce cas, zend allouera et détruira la table des symboles, ce qui entraînera beaucoup de performances. dégradation. En zend, la valeur actuelle de N est 32. Par conséquent, lorsque nous écrivons des programmes PHP, il est préférable de ne pas dépasser 32 niveaux d’appels de fonctions. Bien sûr, s’il s’agit d’une application Web, le niveau d’appel de fonction lui-même peut être profond.
Transfert de paramètres
Contrairement à la fonction intégrée appelant zend_parse_params pour obtenir des paramètres, l'acquisition de paramètres dans les fonctions utilisateur se fait via des instructions . Le nombre de paramètres d’une fonction correspond au nombre d’instructions dont elle dispose. Spécifique à l'implémentation, il s'agit d'une affectation de variable ordinaire.
L'analyse ci-dessus montre que, par rapport aux fonctions intégrées, puisque la table de pile est maintenue par elle-même et que l'exécution de chaque instruction est également une fonction C, la performance de la fonction utilisateur sera relativement bien pire, comme nous le verrons plus tard. Analyse comparative spécifique. Par conséquent, si une fonction possède une fonction PHP intégrée correspondante, essayez de ne pas réécrire la fonction vous-même pour l'implémenter.
Méthode de classe
Le principe d'exécution de la méthode de classe est le même que celui de la fonction utilisateur, et elle est également traduite en opcodes et appelée séquentiellement. L'implémentation de la classe est implémentée par Zend en utilisant une structure de données zend_class_entry, qui stocke certaines informations de base liées à la classe. Cette entrée est traitée lors de la compilation de PHP.
Dans le commun de zend_function, il y a un membre appelé scope, qui pointe vers le zend_class_entry de la classe correspondant à la méthode actuelle. Concernant l'implémentation orientée objet en PHP, je ne donnerai pas ici une introduction plus détaillée, j'écrirai à l'avenir un article spécial pour détailler le principe d'implémentation orientée objet en PHP. En ce qui concerne la fonction, le principe de mise en œuvre de la méthode est exactement le même que celui de la fonction, et ses performances sont théoriquement similaires. Nous ferons une comparaison détaillée des performances plus tard.
L'impact de la longueur du nom de fonction sur les performances
Méthode de test
Pour la longueur du nom 1 , 2, 4, 8 et 16, testez et comparez le nombre de fois qu'elles peuvent être exécutées par seconde et déterminez l'impact de la longueur du nom de la fonction sur les performances
Les résultats des tests sont tels qu'indiqués ci-dessous
Analyse des résultats
On peut voir sur la figure que la longueur du nom de la fonction a toujours un certain impact sur les performances. Une fonction de longueur 1 et un appel de fonction vide de longueur 16 ont une différence de performances de 1x. Il n'est pas difficile d'en trouver la raison en analysant le code source. Comme mentionné ci-dessus, lorsqu'une fonction est appelée, zend interrogera d'abord les informations pertinentes via le nom de la fonction dans une table_fonction globale, qui est une table de hachage. Inévitablement, plus le nom est long, plus l’interrogation prend du temps. Par conséquent, lors de l'écriture d'un programme, il est recommandé que le nom d'une fonction appelée plusieurs fois ne soit pas trop long
Bien que la longueur du nom de la fonction ait un certain impact sur les performances, quelle est sa taille c'est spécifiquement ? Ce problème doit toujours être examiné en fonction de la situation réelle. Si une fonction elle-même est relativement complexe, elle n'aura pas un grand impact sur les performances globales.
Une suggestion est de donner des noms concis et concis aux fonctions qui seront appelées plusieurs fois et qui auront des fonctions relativement simples.
L'impact du nombre de fonctions sur les performances
Méthode de test
Dans ce qui suit trois environnements Effectuez le test d'appel de fonction ci-dessous et analysez les résultats : 1. Le programme contient seulement 1 fonction 2. Le programme contient 100 fonctions 3. Le programme contient 1000 fonctions.
Testez le nombre de fonctions pouvant être appelées par seconde dans ces trois situations
Les résultats des tests sont présentés ci-dessous
Analyse des résultats
Il ressort des résultats des tests que les performances dans ces trois cas sont presque les mêmes. Lorsque le nombre de fonctions augmente, la performance diminue. minime et peut être ignoré.
D'après l'analyse des principes d'implémentation, la seule différence entre plusieurs implémentations est la partie acquisition de fonction. Comme mentionné précédemment, toutes les fonctions sont placées dans une table de hachage et l'efficacité de la recherche doit toujours être proche de O(1) sous différents nombres, la différence de performances n'est donc pas grande.
Consommation de différents types d'appels de fonction
Méthode de test
Sélectionner la fonction utilisateur, la classe méthode, Il existe un type de méthode statique et de fonction intégrée. La fonction elle-même ne fait rien et renvoie directement. Elle teste principalement la consommation d'appels de fonction vides. Les résultats du test sont le nombre d'exécutions par seconde
Afin de supprimer d'autres effets pendant le test, tous les noms de fonctions ont la même longueur
Les résultats du test sont comme indiqué ci-dessous
Analyse des résultats
D'après les résultats des tests, nous pouvons voir que pour les fonctions php écrites par les utilisateurs eux-mêmes, non quel que soit leur type, leur efficacité est presque la même. Environ 280 W/s. Comme prévu, même pour les climatiseurs, l'efficacité de la fonction intégrée est beaucoup plus élevée, atteignant 780 W/s, soit trois fois celle de la première. On peut voir que la surcharge des appels de fonctions intégrés est encore bien inférieure à celle des fonctions utilisateur. De l'analyse de principe précédente, il ressort que la principale lacune réside dans des opérations telles que l'initialisation de la table des symboles et la réception des paramètres lors de l'appel de la fonction utilisateur.
Comparaison des performances entre les fonctions intégrées et les fonctions utilisateur
Méthode de test
Construit- dans les fonctions et fonctions utilisateur Pour comparer les performances, nous sélectionnons ici plusieurs fonctions couramment utilisées, puis utilisons des fonctions PHP pour implémenter les mêmes fonctions pour la comparaison des performances.
Dans le test, nous avons sélectionné un exemple typique de chaînes, de mathématiques et de tableaux à des fins de comparaison. Ces fonctions sont l'interception de chaînes (substr), la conversion décimale en binaire (decbin) et la valeur minimale (min) et les retours. toutes les clés du tableau (array_keys).
Les résultats du test sont présentés ci-dessous
Analyse des résultats
Il peut Comme le montrent les résultats des tests, il s'avère que, comme nous nous y attendions, les performances globales des fonctions intégrées sont bien supérieures à celles des fonctions utilisateur ordinaires. Surtout pour les fonctions impliquant des opérations sur les chaînes, l'écart atteint 1 ordre de grandeur. Par conséquent, un principe d'utilisation des fonctions est que si une certaine fonction a une fonction intégrée correspondante, essayez de l'utiliser au lieu d'écrire la fonction PHP vous-même.
Pour certaines fonctions impliquant un grand nombre d'opérations sur les chaînes, afin d'améliorer les performances, vous pouvez envisager d'utiliser des extensions pour les implémenter. Par exemple, filtrage de texte enrichi courant, etc.
Comparaison des performances avec les fonctions C
Méthode de test
Nous sélectionnons 3 fonctions chacune pour l'opération de chaîne et l'opération arithmétique à des fins de comparaison, et PHP utilise des extensions pour accomplir . Les trois fonctions sont de simples opérations arithmétiques ponctuelles, des comparaisons de chaînes et des opérations arithmétiques multiples.
En plus de ses propres deux types de fonctions, il testera également les performances après suppression de la surcharge de la fonction climatisation. D'une part, il comparera les différences de performances des deux fonctions (C et PHP. intégré), et d'autre part, il vérifiera la climatisation de côté. Consommation de fonction
Le point de test est la consommation de temps pour l'exécution de 100 000 opérations
. Les résultats des tests sont présentés ci-dessous
Analyse des résultats
La différence entre le coût des fonctions intégrées et Les fonctions C sont petites après avoir supprimé l'impact de la fonction de climatisation php. À mesure que les fonctions deviennent de plus en plus complexes, les performances des deux parties se rapprochent. Cela peut être facilement démontré à partir de l'analyse précédente de l'implémentation des fonctions. Après tout, les fonctions intégrées sont implémentées en C.
Plus la fonction est complexe, plus l'écart de performances entre C et PHP est petit
Par rapport au C, la surcharge des appels de fonction PHP est beaucoup plus élevée et les performances des fonctions simples ont toujours un certain impact. Par conséquent, les fonctions en PHP ne doivent pas être imbriquées et encapsulées trop profondément.
Pseudo-fonctions et leurs performances
En php, il existe certaines fonctions, qui sont une utilisation de fonction standard, mais l'implémentation sous-jacente est complètement différentes des appels de fonction réels, ces fonctions n'appartiennent à aucune des trois fonctions mentionnées ci-dessus. Elles constituent essentiellement un opcode distinct, appelé ici pseudo-fonction ou fonction d'instruction.
Comme mentionné ci-dessus, les pseudo-fonctions sont utilisées comme les fonctions standards et semblent avoir les mêmes caractéristiques. Mais lorsqu'ils sont finalement exécutés, ils sont reflétés par zend dans une instruction correspondante (opcode) pour l'appel, leur implémentation est donc plus proche d'opérations telles que if, for et arithmétiques.
Pseudo fonctions en php
1. isset
2. vide
non défini
4. .évaluer
De l'introduction ci-dessus, on peut voir que puisque les pseudo-fonctions sont directement traduites en instructions d'exécution, par rapport aux fonctions ordinaires, il y a une surcharge de moins causée par un appel de fonction, donc les performances seront meilleures. Nous effectuons une comparaison à travers le test suivant. Array_key_exists et isset peuvent déterminer si une clé existe dans le tableau. Jetez un œil à leurs performances
Comme le montre la figure, comparée à array_key_exists, les performances d'isset sont les suivantes. est beaucoup plus élevé, essentiellement environ 4 fois supérieur à celui du premier, et même par rapport aux appels de fonction vides, ses performances sont environ 1 fois supérieures. Cela prouve également que la surcharge des appels de fonctions PHP est encore relativement importante.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!