PHP 8, compilation Just In Time

Antoine Déprez
03/02/2020

La version 8 de PHP s’accompagnera d’un compilateur just in time qui a fait pas mal de bruit dans la communauté. Beaucoup annoncent déjà des gains de performance qui nous font repenser au passage à PHP 7. Mais tout cela est-il justifié ?

Petits rappels sur PHP

PHP est un langage interprété, ce qui signifie que le code n’est pas transformé en fichiers exécutables par votre machine, mais que le code source est lu par la machine pour faire tourner votre programme (eh oui, le code php va en prod…), contrairement aux langages compilés qui permettent de produire un exécutable qui peut être utilisé directement par la machine cible.

Dans le cas de PHP, quand un fichier doit être exécuté, il est d’abord converti en opcode, qui sera à son tour exécuté par une machine virtuelle.
L’un des principaux problème d’un tel mode de fonctionnement, est que l’exécution du programme est bien plus lente que si on avait compilé le langage.

Malgré ce manque de rapidité lié à la conception, plusieurs travaux ont été entrepris pour booster les performances, notamment :

  • Un cache d’opcode : ce système de cache permet au moteur de ne pas refaire à chaque requête la compilation des fichiers PHP vers l’opcode. De tels systèmes de cache existaient déjà depuis pas mal de temps, mais ce n’est que bien plus tard qu’il y en a eu un intégré par défaut dans le coeur de PHP.

  • La réécriture de plusieurs parties de l’interpréteur, pour permettre entre autres une meilleure gestion de la mémoire et des appels de fonctions de bas niveau. Cela a donné naissance à PHP 7, qui proposait des performances jusqu’à deux fois meilleures que la version précédente.

Avant la sortie de PHP 7, un autre acteur majeur a travaillé de son côté sur une amélioration des performances du moteur : il s’agit de Facebook, qui a fini par sortir son propre interpréteur PHP : HHVM.
Une des caractéristiques de HHVM permettant l’exécution plus rapide du code PHP est la compilation “just in time”. HHVM présente ainsi des performances similaires à celles de PHP 7.

Mais au fait, c’est quoi la compilation just in time ?

La compilation “just in time” (JIT)

Si on veut avoir de bonnes performances, une approche “simple” serait de compiler notre programme. Le problème est que ce n’est justement pas si simple : il faut avoir une bonne connaissance du système qui va exécuter notre programme, l’étape de compilation est (très) lente, et si on modifie une petite partie du code, il faut tout recompiler…

La compilation JIT permet une compilation de portions de code au moment de l’exécution. Pour reprendre le cas de PHP : avec la compilation JIT, le code serait d’abord transformé en opcode, qui dans certains cas serait à son tour compilé, et serait enfin exécuté directement par la machine hôte, plutôt que par la machine virtuelle PHP. Le code exécuté ainsi serait alors bien plus rapide.

L’un des problèmes de ce système est que la compilation est très lente, et même si on ne compile qu’une fois le programme, le jeu n’en vaut pas forcément la chandelle… Pour cette raison, le compilateur JIT peut éventuellement ne compiler que certaines parties du code. En plus, comme la compilation est faite à l’exécution, la machine virtuelle va pouvoir laisser le code tourner au moins une fois sans compilation, afin d’identifier quelles parties du code vont le plus bénéficier de la compilation, et comment l’optimiser.
Le fait que le code soit compilé lors de l’exécution permet également aux équipes de développeurs de ne pas avoir à changer leur workflow de mise en production.

Ça a l’air super ! Et si on essayait ça en PHP ?

Le développement du compilateur JIT dans PHP est déjà à un stade bien avancé, et il est d’ailleurs possible de l’essayer. Et les résultats sont bluffants !

Sur des algorithmes exploitant massivement le processeur, comme du calcul de suite de Fibonnacci ou des fonctions mathématiques complexes, on obtient des performances jusqu’à 20 fois supérieures à ce qu’on a sans compilation JIT !

Conclusion, la compilation JIT va révolutionner PHP !

EH MAIS ATTENDS ! T’as pas des vrais exemples ? Moi je fais pas de Fibonacci dans mon appli…

Bon ok… Des benchmarks ont aussi été fait sur des applications PHP “du monde réél”.

Par exemple sur un WordPress : l’évolution des performances est… nulle : pas de changement dans les performances…

On pourrait se dire que quelque chose a mal été fait dans la compilation JIT, mais réfléchissons-y un peu.

Que fait du code php

La compilation JIT permet effectivement une exécution plus rapide du code PHP. Mais en réalité, est-ce que dans une application classique, c’est vraiment la vitesse d’exécution du PHP en lui-même qui détermine la vitesse globale de l’application ?

Dans la plupart des cas, on fait des requêtes sur une base de données, des appels à des API externes, des manipulations sur les fichiers, tout ça pour transformer nos données dans un format attendu pour l’utilisateur : HTML, JSON, XML, ou écrire des données quelque part (fichiers, base de données…)… Et toutes ces opérations de requetage de base de données ou d’api externes ne sont absolument pas impactées par la vitesse d’exécution de notre code PHP…

Les applications PHP sont dites en général “I/O bound” : elles sont limitées en termes de vitesse d’exécution par des processus externes à PHP. Alors que la compilation donne le plus de gains de performances aux applications “CPU bound”, celles qui consomment intensément les ressources du processeur.

En plus de cela, PHP est déjà très rapide, et donne accès à beaucoup de fonctions dans la librairie standard. Fonctions qui sont écrites en… C, un langage compilé, très performant. Et depuis les améliorations apportées avec PHP 7, appeler une fonction codée en C depuis un script PHP, ou appeler directement la même fonction depuis du code machine, revient à des performances comparables.
Attention, il y a effectivement une différence : c’est toujours plus rapide d’appeler la fonction directement depuis du code machine. Mais le gain n’est pas significatif par rapport à tous les appels externes que l’on fait.

Mais alors pourquoi s’embêter à développer un compilateur JIT si ça ne sert à rien dans nos applications ?

Il y a plusieurs raisons possibles à cette volonté d’implémenter un compilateur JIT dans PHP. La première est une question d’ambition. Si PHP est déjà un langage très performant pour des applications web, il ne l’est pas forcément pour d’autres types d’applications qui elles dépendent plus du CPU. L’ajout de ce compilateur pourra permettre d’étendre PHP à d’autres domaines.

Aussi, il faut savoir qu’il y a très peu de développeurs qui travaillent à plein temps sur le développement du moteur PHP. En plus de cela, c’est assez compliqué pour un développeur PHP de se dire qu’il va contribuer au moteur : il est écrit en langage C, ce qui en rebute plus d’un (et moi le premier !).
Introduire un compilateur JIT permettrait à la communauté de contribuer plus facilement à la librairie standard, qui pourrait à présent inclure des fonctions écrites en PHP, sans trop impacter les performances grâce à la compilation JIT.