PHP 7.4 et FFI, ce qu’il faut retenir
(🇬🇧 Read the english version here)
PHP Foreign Function Interface, ou PHP FFI pour les intimes, ou FFI pour les fans, est une extension PHP qui permet d’inclure facilement des bibliothèques externes au sein de PHP. Autrement dit, il est possible d’utiliser directement des librairies partagées écrite en C, Go, Rust, etc. depuis PHP sans avoir besoin de créer d’extension PHP en C. C’est un mécanisme qui existe depuis très longtemps dans d’autre langage comme Python, ce qui a fait entre autre une de ses forces.
Section intitulée generation-d-uuidGénération d’UUID
Prenons un exemple pour se chauffer : la génération d’UUID.
En PHP il existe plusieurs méthodes pour générer un UUID. La meilleure méthode est de passer via la PECL UUID. Vous pouvez retrouver son code sur github. Cette extension PHP se charge d’associer des functions qui seront utilisable par un développeur PHP à des appels sur la libuuid. Pour fonctionner, il faut obligatoirement installer libuuid
sur son système (ce qui est déjà installé la plupart du temps), ainsi que la PECL.
Nous pouvons décrire le fonctionnement d’un appel à une librairie externe depuis PHP comme cela :
+---------------------+
| Votre code PHP |
+---+-------------^---+
v ^
+---v-------------+---+
| Le moteur PHP |
+---+-------------^---+
v ^
+---v-------------+---+
| L'ext UUID |
+---+-------------^---+
v ^
+---v-------------+---+
| La lib UUID |
+---------------------+
La promesse de FFI, c’est de remplacer la couche « extension UUID » par du code PHP.
Avant de parler de la couche extension PHP ou de FFI, il faut expliquer ce qu’est une librairie. Une librairie est habituellement écrite en C. Mais elle peut aussi être écrite dans d’autres langages qui sont capable de se compiler en une librairie partagée : C++, Rust, Go, etc. Sous unix ou linux, la librairie sera compilée en un fichier .so
et sous Windows en un fichier .dll
. Il est aussi possible d’inclure directement une librairie dans un programme, mais ce chapitre ne nous intéresse pas pour cet article.
Dans le code source de la librairie, il existe des fichiers .h
qui contiennent ce que la librairie est capable de faire. Voici un extrait du fichier uuid.h
:
# ...
# Quelques déclarations de constantes :
#define UUID_VARIANT_NCS 0
#define UUID_VARIANT_DCE 1
#define UUID_VARIANT_MICROSOFT 2
#define UUID_VARIANT_OTHER 3
# Quelques declarations de fonctions :
void uuid_generate(uuid_t out);
int uuid_compare(const uuid_t uu1, const uuid_t uu2);
# ...
Un fichier .h
est quelque chose que nous pourrions comparer avec une interface en PHP : il regroupe les constantes et les fonctions exposées par la librairie.
Section intitulée creation-de-la-couche-ffi-uuidCréation de la couche FFI/UUID
Pour fonctionner, FFI a besoin des signatures que nous voulons utiliser de la librairie sous jacente. Nous allons copier le fichier .h
dans notre projet et au besoin, nous nettoierons et adapterons le fichier. Nous pourrions, par exemple, supprimer les fonctions qui ne nous seront pas utiles. Voici à quoi ressemble le fichier final pour notre librairie :
#define FFI_LIB "libuuid.so.1"
typedef unsigned char uuid_t[16];
extern void uuid_generate_time(uuid_t out); // v1
extern void uuid_generate_md5(uuid_t out, const uuid_t ns, const char *name, size_t len); // v3
extern void uuid_generate_random(uuid_t out); // v4
extern void uuid_generate_sha1(uuid_t out, const uuid_t ns, const char *name, size_t len); // v5
Ce travail est la partie la plus importante et la plus fastidieuse. Un fois effectué, on charge ce fichier dans PHP/FFI :
$ffi = FFI::load(__DIR__ . '/include/uuid-php.h');
Et Voilà ! Nous sommes maintenant en mesure d’utiliser libuuid
directement depuis PHP. Cependant, les fonctions de libuuid
attendent des paramètres en entrée d’un certain type. Comme vous avez pu le voir dans la signature des fonctions, celles-ci ne retournent pas un UUID, mais vont modifier la première valeur passée par référence. Nous devons créer cette valeur en amont avant de pouvoir utiliser la fonction :
$output = $ffi->new('uuid_t');
$output
est une instance de type FFI\CData
. Suivant le type interne de CDATA, nous pourrons accéder aux différents valeurs décrites dans la documentation.
Enfin, nous pouvons appeler notre fonction. uuid_generate_random
correspond au nom exposé par la librairie dans le fichier .h
:
$ffi->uuid_generate_random($output);
Le contenu de $output
va être mis à jour avec un tableau contenant les valeurs (decimal) de notre UUID. Il ne reste plus qu’à convertir ce tableau en une string contenant les valeurs (hexadécimal) :
foreach ($output as $values[]);
$uuid = sprintf('%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x', ...$values);
Simple, non ? Si vous ne voulez pas vous embêter à refaire tout ce code pour supporter la libuuid
, nous avons open-sourcé un paquet pour vous : https://github.com/jolicode/ffi-uuid🍾
Section intitulée quelques-considerationsQuelques considérations
Section intitulée simpliciteSimplicité
Le binding d’une librairie externe est relativement simple. Le plus compliqué est la création d’un fichier .h
minimal, ainsi qu’un mapping des types PHP vers les types de la librairie et vice-versa.
Section intitulée performancePerformance
Il est aussi intéressant de regarder les performances de notre implémentation. Dans le dépôt de code vous pourrez retrouver un script de benchmark. Voilà les résultats lorsque nous comparons la PECL à notre code :
FFI:
* [v1] 1.254s
* [v4] 5.301s
PECL:
* [v1] 0.626s
* [v4] 4.583s
Nous pouvons voir que la PECL est deux fois plus rapide que notre implémentation pour la version 1 mais seulement 15% plus rapide pour la version 4. Cela peut s’expliquer assez facilement : un UUID v4 est composé exclusivement de nombres aléatoires, alors que la version 1 contient des blocs de nombres invariants. La récupération de nombres aléatoires est quelque chose d’un peu lent, c’est pourquoi un UUID v4 est beaucoup plus lent à générer qu’un UUID v1. Le différence entre les deux implémentations est moins visible sur la v4 car le temps de génération est essentiellement passé dans la libuuid
.
Section intitulée que-pouvons-nous-en-conclureQue pouvons nous en conclure ?
FFI est encore très jeune, nous pouvons nous attendre à des améliorations de performance. Cependant, nous pouvons déjà tirer quelques conclusions :
- Si une extension native à PHP existe et que vous pouvez l’installer : utilisez la ;
- Si l’extension n’existe pas, alors FFI est une très bonne alternative ;
- Si vous avez un bottleneck dans votre application, il peut être intéressant de porter cette partie de code en C, Rust, ou Go, et de binder ce code avec FFI. FFI va devenir très intéressant lorsque l’on doit faire beaucoup de calcul CPU (gestion de DOM, de gros tableau, de calcul mathématique).
Section intitulée les-extensions-php-vont-elles-etre-remplacees-par-ffiLes extensions PHP vont elles être remplacées par FFI ?
Il est beaucoup trop tôt pour répondre à cette question qui a le mérite d’être posée. Cependant, des extensions comme PDO font beaucoup plus qu’un simple binding à une lib sous jacente. Je suis très confiant sur le fait que ces extensions ne seront pas remplacées par FFI.
Cependant d’autres extensions plus simple pourront sûrement être remplacées. C’est le cas de php-redis, amqp, uuid, etc. D’ailleurs Remi Collet à déjà commencé à jouer avec FFI pour remplacer l’extension redis.
FFI ouvre aussi les portes au remplacement de certaines lib écrites en pur PHP alors que des lib bas niveau existent. C’est le cas de gitlib qui pourrait être remplacée par un portage FFI de libgit2
Et enfin, dans certains cas, il n’existe ni extension C, ni implémentation PHP. Si vous avez déjà voulu tester TensorFlow via PHP, vous avez pu vous rendre compte que … c’est compliqué. Dmitry Stogov, l’un des plus important contributeur à PHP – mais aussi l’auteur de PHP/FFI, a créé un POC pour binder tensor flow à PHP.
Section intitulée quel-langage-choisir-pour-binder-une-lib-en-phpQuel langage choisir pour binder une lib en PHP ?
Tous les langages qui peuvent compiler une librairie partagée (.so) ne sont pas forcément adaptés à être binder en FFI. Il est préférable d’utiliser des langages sans runtime (C / C++ / Rust / …) car inclure un runtime peut avoir des conséquences non souhaitées selon ce que fait la librairie. En Go par exemple, le runtime va être capable de gérer un garbage collector ou encore une event loop (pour les goroutines). Si jamais votre programme PHP gère aussi une event loop, il peut arriver que certains événements ne soient jamais dispatchés à votre librairie partagée et donc que les goroutines ne soient jamais exécutées.
Section intitulée comment-binder-une-lib-rust-en-phpComment binder une lib Rust en PHP ?
J’ai voulu tester s’il était plus rapide d’exécuter des calculs un peu compliqués dans un autre langage. C’est assez fréquent d’avoir à extraire des informations d’une page web, que ce soit pour des tests fonctionnels, ou pour faire un crawler.
Joel a fait une petite librairie qui permet d’extraire le premier élément HTML d’un document qui matche une expression CSS. Le code est très simple, à tel point que la conversion de type C vers Rust représente plus d’un tiers du projet.
Le binding PHP quand à lui ressemble fortement à ce que nous avons vu précédemment :
$ffi = FFI::cdef(<<<EOH
const char *cssfilter(const char *html, const char *filter);
EOH, __DIR__.'/../target/release/libcssfilter.so');
Et son utilisation est même encore plus simple :
$value = $ffi->cssfilter($html, $selector);
Les performances sont assez impressionnantes et encourageantes :
FFI:
Duration: 1.731s
symfony/crawler:
Duration: 2.321s
Nous pouvons facilement en conclure que si les calculs sont compliqués, il peut être très intéressant de porter une partie du code dans un autre langage pour améliorer les performances de notre application.
Section intitulée conclusionConclusion
FFI n’est pas encore sorti officiellement, mais j’en suis déjà fan 🤩. FFI va nous permettre de tester rapidement des librairies alors que les bindings officiels ne sont pas encore disponibles. Il va aussi nous permettre de remplacer certaines parties du code qui sont trop lentes à nos yeux par une implémentation en Rust ou autre.
Bref, l’essayer, c’est l’adopter.
Commentaires et discussions
UUID generation in PHP
We have been using UUID for years and different ways to generate UUID exist. I prefer the PECL extension over the ramsey/uuid because it’s simpler and more straightforward. Few weeks ago, with Nicolas Grekas we had the idea to port the PECL extension to plain PHP as a Symfony Polyfill.…
PHP 7.4 FFI: What you need to know
(🇫🇷 Lire la version en Français ici) PHP Foreign Function Interface, or FFI for fans, is a PHP extension that allows you to include with ease some externals libraries into your PHP code. That means it’s possible to use C, Go, Rust, etc. shared library directly in PHP without writing…
Lire la suite de l’article PHP 7.4 FFI: What you need to know
Nos articles sur le même sujet
Nos formations sur ce sujet
Notre expertise est aussi disponible sous forme de formations professionnelles !
Symfony avancée
Découvrez les fonctionnalités et concepts avancés de Symfony
Ces clients ont profité de notre expertise
La refonte de la plateforme odealarose.com par JoliCode a représenté une transformation complète de tous les aspects de l’application. En charge de l’aspect technique, nous avons collaboré avec Digital Ping Pong (société cofondée par JoliCode), qui a eu en charge à la conception en revisitant entièrement le parcours client et l’esthétique de la plateforme…
Groupama confie à JoliCode la maintenance et les évolutions de son outil de souscription d’épargne salariale. Pierre angulaire de l’acquisition de nouveau client, l’outil permet aux apporteurs de saisir toutes les données de l’entreprise, faire signer numériquement le contrat au client et de conclure la souscription. Le tunnel est accompagné d’outil…
Au fil de notre collaboration avec Deezer, nous avons été impliqués dans plusieurs initiatives visant à optimiser les performances de leur plateforme. Notre engagement initial s’est concentré sur le soutien et le conseil à l’équipe « Product Features » lors de leur projet de migration en cours. Nous avons apporté notre expertise pour résoudre…