
Dans l'Ă©cosystĂšme PHP, il existe actuellement deux connecteurs pour travailler avec le serveur Tarantool â c'est l'extension officielle PECL , Ă©crite en C, et , Ă©crite en PHP. Je suis l'auteur de ce dernier.
Dans cet article, je voudrais partager les résultats des tests de performance des deux bibliothÚques et montrer comment, avec des modifications minimes du code, on peut atteindre une augmentation de 3 à 5 % de performance (sur des tests synthétiques !).
Qu'allons-nous tester ?
Nous testerons les connecteurs mentionnĂ©s ci-dessus synchrones , lancĂ©s de maniĂšre asynchrone, parallĂšlement et de maniĂšre asynchrone-parallĂšle. đ De plus, nous ne voulons pas toucher le code des connecteurs eux-mĂȘmes. Actuellement, plusieurs extensions sont disponibles pour atteindre l'objectif souhaitĂ© :
- â un framework asynchrone haute performance pour PHP. UtilisĂ© par des gĂ©ants d'internet comme Alibaba et Baidu. Depuis la version 4.1.0, la mĂ©thode magique SwooleRuntime::enableCoroutine(), permet « en une ligne de code de transformer les bibliothĂšques rĂ©seau synchrones PHP en asynchrones ».
- Async â jusqu'Ă rĂ©cemment, une extension trĂšs prometteuse pour le travail asynchrone en PHP. Pourquoi jusqu'Ă rĂ©cemment ? Malheureusement, pour une raison qui m'est inconnue, l'auteur a supprimĂ© le dĂ©pĂŽt et l'avenir du projet est incertain. Il nous faudra utiliser des forks. Comme Swoole, cette extension permet en un geste de remplacer l'implĂ©mentation standard des flux TCP et TLS par leurs versions asynchrones. Cela se fait via l'option «async.tcp = 1«.
- â une extension assez rĂ©cente de Joe Watkins, l'auteur de bibliothĂšques comme phpdbg, apcu, pthreads, pcov, uopz. L'extension fournit une API pour le travail multithreadĂ© en PHP et se positionne comme un remplacement pour pthreads. Une limitation importante de la bibliothĂšque est qu'elle ne fonctionne qu'avec la version ZTS (Zend Thread Safe) de PHP.
Comment allons-nous tester ?
Nous allons lancer une instance de Tarantool sans journal d'Ă©criture anticipĂ©e (wal_mode = none) et avec un tampon rĂ©seau agrandi (readahead = 1 * 1024 * 1024). La premiĂšre option exclura les opĂ©rations d'Ă©criture sur disque, la seconde permettra de lire plus de requĂȘtes Ă partir du tampon du systĂšme d'exploitation, minimisant ainsi le nombre d'appels systĂšme.
Pour les benchmarks qui fonctionnent avec des donnĂ©es (insertion, suppression, lecture, etc.), un espace memtx sera (re)créé avant le dĂ©but du benchmark, oĂč les valeurs de l'index primaire sont gĂ©nĂ©rĂ©es par un gĂ©nĂ©rateur de valeurs entiĂšres sĂ©quentielles.
La DDL de l'espace est la suivante :
space = box.schema.space.create(config.space_name, {id = config.space_id, temporary = true})
space:create_index('primary', {type = 'tree', parts = {1, 'unsigned'}, sequence = true})
space:format({{name = 'id', type = 'unsigned'}, {name = 'name', type = 'string', is_nullable = false}})Si nécessaire, avant de lancer le benchmark, l'espace est rempli avec 10 000 tuples de la forme
{id, "tuplД_"}L'accÚs aux tuples se fait par une valeur de clé aléatoire.
Le benchmark lui-mĂȘme est une seule requĂȘte au serveur qui est exĂ©cutĂ©e 10 000 fois (rĂ©volutions), qui, Ă leur tour, sont effectuĂ©es en itĂ©rations. Les itĂ©rations se rĂ©pĂštent tant que toutes les dĂ©viations de temps entre 5 itĂ©rations ne se situent pas dans une marge d'erreur acceptable de 3 %*. AprĂšs cela, le rĂ©sultat moyen est pris. Entre les itĂ©rations, il y a une pause d'1 seconde, afin de ne pas permettre au processeur de passer en throttling. Le collecteur de dĂ©chets Lua est dĂ©sactivĂ© avant chaque itĂ©ration et est forcĂ© de s'exĂ©cuter aprĂšs sa terminaison. Le processus PHP est lancĂ© uniquement avec les extensions nĂ©cessaires pour le benchmark, avec la mise en tampon de sortie activĂ©e et le collecteur de dĂ©chets dĂ©sactivĂ©.
* Le nombre de rĂ©volutions, d'itĂ©rations et le seuil d'erreur peuvent ĂȘtre modifiĂ©s dans les paramĂštres du benchmark.
Environnement de test
Les rĂ©sultats publiĂ©s ci-dessous ont Ă©tĂ© rĂ©alisĂ©s sur un MacBookPro (2015), systĂšme d'exploitation â Fedora 30 (version du noyau 5.3.8-200.fc30.x86_64). Tarantool a Ă©tĂ© exĂ©cutĂ© dans Docker avec le paramĂštre--network host".
Versions des paquets :
Tarantool : 2.3.0-115-g5ba5ed37e
Docker : 19.03.3, build a872fc2f86
PHP : 7.3.11 (cli) (construit : 22 oct. 2019 08:11:04)
tarantool/client : 0.6.0
rybakit/msgpack : 0.6.1
ext-tarantool : 0.3.2 (+ patch pour 7.3)*
ext-msgpack : 2.0.3
ext-async : 0.3.0-8c1da46
ext-swoole : 4.4.12
ext-parallel : 1.1.3
* Malheureusement, le connecteur officiel ne fonctionne pas avec la version PHP > 7.2. Pour compiler et exécuter l'extension sur PHP 7.3, il a fallu utiliser .
Résultats
Mode synchrone
Le protocole de Tarantool utilise un format binaire pour la sérialisation des messages. Dans le connecteur PECL, la sérialisation est profondément cachée au sein de la bibliothÚque et il n'est pas possible d'influencer le processus d'encodage depuis le code userland . Le connecteur en PHP pur, quant à lui, offre la possibilité de personnaliser le processus de codage en étendant le codeur standard ou en utilisant sa propre implémentation. Deux codeurs sont disponibles par défaut, l'un basé sur (l'extension officielle MessagePack PECL), l'autre étant basé sur (en PHP pur).
Avant de comparer les connecteurs, mesurons les performances des codeurs MessagePack pour le connecteur PHP, et lors des tests suivants, nous utiliserons celui qui montrera le meilleur résultat :

Bien que la version PHP (Pure) soit moins rapide que l'extension PECL, dans des projets rĂ©els, je recommanderais tout de mĂȘme d'utiliser , car dans l'extension officielle MessagePack, la spĂ©cification du format n'est mise en Ćuvre que partiellement (par exemple, il n'y a pas de support pour les types de donnĂ©es personnalisĂ©s, sans lesquels vous ne pourrez pas utiliser Decimal - un nouveau type de donnĂ©es introduit dans Tarantool 2.3) et prĂ©sente plusieurs autres (y compris des problĂšmes de compatibilitĂ© avec PHP 7.4). Dans l'ensemble, le projet semble abandonnĂ©.
Mesurons donc les performances des connecteurs en mode synchrone :

Comme le montre le graphique, le connecteur PECL (Tarantool) affiche de meilleures performances par rapport au connecteur en PHP (Client). Ce n'est pas surprenant, compte tenu du fait que ce dernier, en plus d'ĂȘtre implĂ©mentĂ© dans un langage plus lent, effectue en rĂ©alitĂ© plus de travail : Ă chaque appel, un nouvel objet est créé Request et Response (dans le cas de Select â aussi Criteria, et dans le cas de Update/Upsert â Operations), des entitĂ©s distinctes Connexion, Packer et Handler ajoutent Ă©galement un surcoĂ»t. Il est Ă©vident que la flexibilitĂ© a un prix. Cependant, dans l'ensemble, l'interprĂ©teur PHP montre de bonnes performances, mĂȘme si la diffĂ©rence existe, elle est minime et sera probablement encore plus faible avec l'utilisation de preloading dans PHP 7.4, sans parler de JIT dans PHP 8.
Continuons. Dans Tarantool 2.0, le support de SQL a été ajouté. Essayons d'effectuer des opérations Select, Insert, Update et Delete en utilisant le protocole SQL et comparons les résultats avec leurs équivalents noSQL (binaires) :

Les résultats SQL ne sont pas trÚs impressionnants (je rappelle que nous testons toujours le mode synchrone). Cependant, je ne voudrais pas me décourager à ce sujet trop tÎt, car le support de SQL est encore en développement actif (récemment, par exemple, le support de ) a été ajouté, et d'aprÚs la liste , le moteur SQL attendra d'autres optimisations.
Async
Voyons maintenant comment l'extension Async peut nous aider à améliorer les résultats ci-dessus. Pour écrire des programmes asynchrones, l'extension fournit une API basée sur des coroutines, que nous allons utiliser. Par expérimentation, nous découvrons que le nombre optimal de coroutines pour notre environnement est de 25 :

Nous 'étalons' 10 000 opérations sur 25 coroutines et voyons ce que cela donne :

Le nombre d'opérations par seconde a plus que triplé pour !
Malheureusement, le connecteur PECL ne s'est pas lancé avec ext-async.
Qu'en est-il de SQL ?

Comme vous le voyez, en mode asynchrone, la différence entre le protocole binaire et SQL est dans les limites de l'erreur.
Swoole
Nous découvrons à nouveau le nombre optimal de coroutines, cette fois pour Swoole :

Nous nous limiterons Ă 25. RĂ©pĂ©tons la mĂȘme astuce qu'avec l'extension Async : rĂ©partissons 10 000 opĂ©rations entre 25 coroutines. De plus, nous ajouterons un autre test, dans lequel nous diviserons tout le travail en 2 processus (c'est-Ă -dire que chaque processus effectuera 5 000 opĂ©rations dans 25 coroutines). Les processus seront créés Ă l'aide de SwooleProcess.
Résultats :

Swoole montre légÚrement de moins bons résultats par rapport à Async lors de l'exécution dans un seul processus, mais avec 2 processus, la situation change radicalement (le nombre 2 n'est pas choisi au hasard, sur ma machine, 2 processus ont montré les meilleurs résultats).
D'ailleurs, l'extension Async a aussi une API pour travailler avec des processus, mais je n'ai pas remarqué de différences significatives lors de l'exécution des benchmarks dans un ou plusieurs processus (il est possible que j'aie fait une erreur quelque part).
SQL vs protocole binaire :

Tout comme avec Async, la différence entre les opérations binaires et SQL est atténuée en mode asynchrone.
Parallel
Puisque l'extension Parallel ne concerne pas les coroutines, mais les threads, mesurons le nombre optimal de threads parallĂšles :

Il est de 16 sur ma machine. Lançons des benchmarks de connecteurs sur 16 threads parallÚles :

Comme vous le voyez, le rĂ©sultat est mĂȘme meilleur qu'avec les extensions asynchrones (Ă l'exception de Swoole exĂ©cutĂ© sur 2 processus). Notez que pour le connecteur PECL, il n'y a rien sur les opĂ©rations Update et Upsert. Cela est dĂ» au fait que ces opĂ©rations ont Ă©chouĂ© avec une erreur - je ne sais pas si c'est Ă cause de ext-parallel, ext-tarantool ou des deux.
Comparons maintenant les performances de SQL :

Avez-vous remarqué la similitude avec le graphique des connecteurs exécutés de maniÚre synchrone ?
Tout ensemble
Enfin, rassemblons tous les rĂ©sultats dans un seul graphique pour voir la vue d'ensemble des extensions testĂ©es. Nous allons ajouter un nouveau test que nous n'avons pas encore effectuĂ© : exĂ©cuter des coroutines Async en parallĂšle avec Parallel*. L'idĂ©e d'intĂ©grer les extensions mentionnĂ©es ci-dessus a dĂ©jĂ par les auteurs, mais aucun consensus n'a Ă©tĂ© atteint, il va donc falloir le faire soi-mĂȘme.
* Lancer des coroutines Swoole avec Parallel n'a pas fonctionné, il semble que ces extensions soient incompatibles.
Voici donc les résultats finaux :

En conclusion
à mon avis, les résultats sont assez remarquables, et j'ai l'impression que ce n'est pas encore la limite ! Il vous revient de décider si cela vous est nécessaire dans un projet réel, je dirai seulement que pour moi, cela a été une expérience intéressante, permettant d'évaluer combien on peut « tirer » d'un connecteur TCP synchronisé avec un minimum d'efforts. Si vous avez des idées pour améliorer les benchmarks, je considérerai avec plaisir votre pull request. L'ensemble du code avec les instructions de lancement et les résultats a été publié dans un .
Source : habr.com
