Les ordinateurs ne comprennent pas vraiment les langages de programmation ; ils comprennent des instructions de très bas niveau qu'aucun humain ne pourrait écrire à la main. Il existe de nombreuses façons de passer d'un langage lisible par l'homme comme PHP ou Rust à un ensemble d'instructions compréhensibles par l'ordinateur.
La méthode la plus simple, et généralement la plus performante, consiste à compiler le code source convivial directement en instructions CPU "Ahead-of-Time" (AOT). Ces instructions sont ensuite enregistrées dans un fichier exécutable autonome "binaire". Parmi les langages courants qui adoptent cette approche, citons C, C++ et Rust.
La méthode de traduction la moins performante, mais généralement la plus facile à écrire, est celle des langages interprétés, souvent appelés "langages de script". Dans ce cas, il y a un programme "interprète" qui traduit chaque déclaration du code source en code machine au fur et à mesure qu'il est "exécuté". PHP 3 fonctionnait de cette manière, c'est pourquoi PHP 3 était si incroyablement lent comparé au PHP moderne.
Une autre approche consiste à utiliser une "machine virtuelle". Une machine virtuelle fonctionne de la même manière qu'un interpréteur, mais elle convertit d'abord le code source en un langage plus simple, essentiellement un langage de script de très bas niveau, qui peut ensuite être interprété beaucoup plus rapidement. Cette approche est très populaire de nos jours car elle offre un bon équilibre entre la complexité, la facilité de développement et la performance. Parfois, cette conversion en "langage plus simple" se fait à l'avance, comme en Java, C# ou Go, etparfois à la volée, comme en PHP, Python ou Javascript. Java appelle cette version simplifiée "bytecode". PHP utilise le terme "opcodes". L'idée est la même.
La dernière tendance en matière de compilation est l'introduction d'un compilateur "juste à temps", ou JIT. Un compilateur JIT PHP commence avec le langage intermédiaire simplifié, et plutôt que de l'interpréter, il le convertit à la volée en code machine, stocke ce code machine en mémoire, et l'exécute.
Les compilateurs JIT de PHP sont très délicats, car pour obtenir de bonnes performances, il faut généralement être sélectif dans les parties du langage intermédiaire qui sont compilées en code machine et celles qui ne le sont pas. Il n'est pas toujours plus rapide de convertir en code machine, en fonction des détails spécifiques du code et du langage en question. De plus, le processus de conversion du code simplifié en code machine natif peut prendre plus de temps que d'exécuter le code simplifié une seule fois et d'en finir avec lui.
Pour cette raison, la plupart des compilateurs PHP JIT analysent le code en cours d'exécution afin d'identifier les parties les plus rentables, puis compilent uniquement ces parties. Le résultat net, en théorie, est que le programme devient littéralement plus rapide au fur et à mesure qu'il s'exécute et que le compilateur PHP JIT de la machine virtuelle apprend quelles parties du code doivent être optimisées.
Java a été le premier langage répandu à inclure un JIT dans sa machine virtuelle. La plupart des moteurs Javascript majeurs le font également. Et, depuis PHP 8.0, PHP a rejoint cette liste.
La nouvelle JIT de PHP a été longue à venir. Il est en fait en cours de développement depuis plusieurs années et a presque été livré sous une forme antérieure en PHP 7.4. Le travail pour rendre PHP compatible avec le JIT a été l'impulsion qui a conduit à la réécriture majeure du moteur qui a donné à la version 7.0 son énorme augmentation de performance.
Le JIT de PHP est construit comme une extension du cache d'opcode. Cela signifie qu'il peut être activé ou désactivé soit lors de la construction de PHP, soit au moment de l'exécution, via php.ini.
L'extension JIT est désactivée par défaut. Elle peut être activée dans le php.ini
en définissant opcache.jit_buffer_size
à une valeur non nulle. Cela permet de contrôler l'espace mémoire que le JIT peut occuper avec son code machine optimisé. Plus n'est pas toujours mieux, cependant, car la JIT peut aussi perdre du temps à compiler du code qui n'a pas vraiment besoin d'être compilé.
L'autre paramètre principal est opcache.jit
, qui contrôle quatre niveaux d'agressivité de la JIT. Ces niveaux sont représentés par des nombres à 4 chiffres, bien qu'il ne s'agisse pas de valeurs numériques mais de quatre contrôles d'agressivité différents. La RFC et la documentation contiennent plus de détails, nous n'entrerons donc pas dans les détails ici.
Il n'existe pas de configuration universelle optimale pour l'ECE. Comme c'est souvent le cas avec des outils avancés comme celui-ci, vous devrez expérimenter avec votre propre application et la régler de manière appropriée.
L'ECE améliore-t-elle les performances ? La réponse prévisible, comme toujours, est "ça dépend". Pour les applications web, peut-être un peu. Pour PHP en tant qu'écosystème, immensément.
PHP, de par sa conception, fonctionne habituellement dans une configuration "shared-nothing". Après avoir traité chaque requête, le programme se termine entièrement. Cela donne au JIT très peu de temps pour analyser et optimiser le code, d'autant plus que la plupart du code d'une requête web typique n'est exécuté qu'une seule fois, car la requête est traitée de manière linéaire. En outre, la plus grande partie de ces applications est souvent constituée d'E/S (communication avec la base de données, principalement), et la JIT ne peut pas du tout y contribuer. Les benchmarks publiés jusqu'à présent montrent que la JIT n'apporte qu'une amélioration marginale des performances dans les applications PHP typiques exécutées via PHP-FPM ou Apache.
Là où la JIT a le potentiel d'être vraiment utile, c'est dans les cas d'utilisation où PHP n'est souvent pas considéré aujourd'hui. Les démons persistants, les analyseurs, l'apprentissage automatique, et d'autres processus longs et intensifs en termes de CPU sont là où se trouvent les vrais avantages. Tout comme le support desinterfaces de fonctions étrangères (FFI) ajouté à PHP 7.4, le but ici est de permettre à PHP de sortir de son statut de langage web de première classe pour devenir un langage de serveur général de première classe.
PHP-Parser est "un analyseur PHP écrit en PHP". Il provient du même Nikita Popov que nous avons mentionné tout au long de cette série et est utilisé par de nombreux outils d'analyse statique sur le marché aujourd'hui, tels que PHPStan. Nikita a rapporté que PHP-Parser fonctionne dans certains cas jusqu'à deux fois plus vite avec le nouveau moteur JIT.
Les applications persistantes comme React PHP ou AmPHP verront probablement une amélioration notable, bien que pas tout à fait autant car elles ont tendance à faire beaucoup d'E/S (où le JIT n'est pas utile).
Il existe des bibliothèques d'apprentissage automatique pour PHP, telles que Rubix ML ou PHP-ML. Elles ne sont pas aussi largement utilisées que leurs équivalents Python, en partie parce que, étant interprétées, elles ont tendance à être plus lentes que les bibliothèques C avec de belles enveloppes Python. Avec une JIT, cependant, ces tâches gourmandes en ressources CPU peuvent finir par être aussi rapides, voire plus rapides, que celles disponibles dans d'autres langages.
PHP n'est plus seulement le plus rapide des principaux langages de script web. C'est désormais un langage de traitement de données général viable et performant, qui met les travailleurs persistants, l'apprentissage automatique et d'autres tâches à forte intensité de CPU entre les mains de millions de développeurs PHP existants à travers le monde.
Nous devons principalement remercier Dmitry Stogov et Zeev Suraski pour l'effort pluriannuel qui a permis à ce RFC de voir le jour.