
I økosystemet PHP eksisterer det for øyeblikket to koblingsprogrammer for å jobbe med Tarantool-serveren – dette er den offisielle PECL-utvidelsen , skrevet i C, og , skrevet i PHP. Jeg er forfatteren av den siste.
I denne artikkelen ønsker jeg å dele resultatene fra ytelsestesting av begge bibliotekene og vise hvordan man med minimale endringer i koden kan oppnå en 3-5% økning i ytelse (på syntetiske tester!).
Hva skal vi teste?
Vi skal teste de ovennevnte synkrone koblingsprogrammene, kjørt asynkront, parallelt og asynkront-parallelt. 🙂 Vi ønsker heller ikke å røre ved koden til koblingsprogrammene selv. For øyeblikket er det tilgjengelig flere utvidelser som kan oppnå det ønskede:
- – et høytytende asynkront rammeverk for PHP. Brukes av slike internett-giganter som Alibaba og Baidu. Fra versjon 4.1.0 ble det introdusert en magisk metode SwooleRuntime::enableCoroutine(), som lar «bare en linje med kode konvertere synkrone nettbiblioteker til asynkrone».
- Async – inntil nylig var det en ganske lovende utvidelse for asynkron arbeid i PHP. Hvorfor inntil nylig? Dessverre, av en grunn jeg ikke kjenner, har forfatteren fjernet repositoriet, og prosjektets videre skjebne er uklar. Vi må bruke av forkene. Som Swoole lar denne utvidelsen deg på en enkel måte aktivere asynkronitet ved å erstatte de standard TCP og TLS implementasjonene med deres asynkrone versjoner. Dette gjøres ved hjelp av alternativet «async.tcp = 1«.
- – en relativt ny utvidelse fra den kjente Joe Watkins, skaperen av biblioteker som phpdbg, apcu, pthreads, pcov, uopz. Utvidelsen gir et API for flertrådet arbeid i PHP og posisjoneres som en erstatning for pthreads. En betydelig begrensning ved biblioteket er at det kun fungerer med ZTS (Zend Thread Safe) versjonen av PHP.
Hvordan skal vi teste?
Vi starter en instans av Tarantool med skrivestoppet forhåndsinnlogging deaktivert (wal_mode = none) og med en økt nettverksbuffer (readahead = 1 * 1024 * 1024). Det første alternativet utelukker diskoperasjoner, mens det andre gir mulighet til å lese flere forespørseler fra operativsystemets buffer og dermed minimere antall systemanrop.
For benchmarks that work with data (insertion, deletion, reading, etc.), a memtx-space will be (re)created before the benchmark starts, in which the values of the primary index are generated by a sequential integer value generator (sequence).
The DDL of the space looks like this:
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}})If necessary, before running the benchmark, the space is filled with 10,000 tuples of the form
{id, "tuple_"}Access to tuples is performed based on a random key value.
The benchmark itself represents a single request to the server, which is performed 10,000 times (revolutions), which, in turn, are carried out in iterations. The iterations are repeated until all time deviations between 5 iterations fall within an acceptable error margin of 3%*. After that, an average result is taken. There is a 1-second pause between iterations to prevent the CPU from throttling. The Lua garbage collector is disabled before each iteration and forced to run after it is completed. The PHP process is started only with the necessary extensions for the benchmark, with output buffering enabled and garbage collection disabled.
* The number of revolutions, iterations, and the error margin threshold can be changed in the benchmark settings.
Testing environment
The results published below were obtained on a MacBookPro (2015), operating system — Fedora 30 (kernel version 5.3.8-200.fc30.x86_64). Tarantool was launched in Docker with the parameter "--network host".
Package versions:
Tarantool: 2.3.0-115-g5ba5ed37e
Docker: 19.03.3, build a872fc2f86
PHP: 7.3.11 (cli) (built: Oct 22 2019 08:11:04)
tarantool/client: 0.6.0
rybakit/msgpack: 0.6.1
ext-tarantool: 0.3.2 (+ patch for 7.3)*
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-parallel: 1.1.3
* Unfortunately, the official connector does not work with PHP version > 7.2. To compile and run the extension on PHP 7.3, it was necessary to use .
Resultater
Synchronous mode
Tarantool's protocol uses a binary format for serializing messages. In the PECL connector, serialization is deeply hidden within the library and it is not possible to affect the encoding process from userland code . En connector i ren PHP gir mulighet for å tilpasse kodingsprosessen ved å utvide den standard koderen eller ved å bruke sin egen implementering. Ut av boksen er to kodere tilgjengelige, en basert på (offisiell MessagePack PECL-utvidelse), den andre er basert på (i ren PHP).
Før vi sammenligner tilkoblerne, la oss måle ytelsen til MessagePack-kodere for PHP-tilkobleren, og i de videre testene vil vi bruke den som gir best resultat:

Selv om PHP-versjonen (Pure) er tregere enn PECL-utvidelsen, vil jeg i virkelige prosjekter fortsatt anbefale å bruke nettopp , fordi i den offisielle MessagePack-utvidelsen er spesifikasjonen for formatet bare delvis implementert (for eksempel er det ikke støtte for brukerdefinerte datatyper, uten hvilken du ikke vil kunne bruke Decimal — den nye datatypen introdusert i Tarantool 2.3) og har en rekke andre (inkludert kompatibilitetsproblemer med PHP 7.4). Generelt ser prosjektet ut som om det er forlatt.
Så, la oss måle ytelsen til tilkoblerne i synkront modus:

Som det fremgår av grafen, viser PECL-tilkobleren (Tarantool) bedre ytelse sammenlignet med tilkobleren i PHP (Client). Men det er ikke overraskende, gitt at sistnevnte, i tillegg til at det er implementert i et tregere språk, også gjør mer arbeid: ved hvert kall opprettes et nytt objekt Request og Response (i tilfelle Select — også Criteria, og i tilfelle Update/Upsert — Operations), separate entiteter Connection, Packer og Handler tilføyer også overhead. Det er åpenbart at fleksibilitet har sin pris. Likevel viser PHP-tolkeren god ytelse, selv om det er en forskjell, er den ikke betydelig, og kanskje vil den bli enda mindre med bruk av preloading i PHP 7.4, for ikke å snakke om JIT i PHP 8.
La oss gå videre. Tarantool 2.0 fikk støtte for SQL. La oss prøve å utføre Select-, Insert-, Update- og Delete-operasjoner ved hjelp av SQL-protokollen og sammenligne resultatene med noSQL (binariske) ekvivalenter:

SQL-resultatene er ikke særlig imponerende (jeg minner om at vi fortsatt tester synkront modus). Men jeg ville ikke blitt bekymret for dette for tidlig, ettersom SQL-støtte fortsatt er under aktiv utvikling (relativt nylig, for eksempel, ble støtte for ) lagt til, og ifølge listen , venter SQL-motoren en rekke optimaliseringer i fremtiden.
Asynkron
La oss nå se hvordan Async-utvidelsen kan hjelpe oss med å forbedre resultatene ovenfor. For å skrive asynkrone programmer gir utvidelsen et API basert på korutiner (coroutines), og det er det vi skal bruke. Gjennom eksperimentering finner vi ut at det optimale antallet korutiner for vårt miljø er 25:

Vi "fordeler" 10 000 operasjoner på 25 korutiner og ser hva vi får:

Antallet operasjoner per sekund har økt med mer enn tre ganger for !
Det er trist, men PECL-kontakten klarte ikke å starte med ext-async.
Hva med SQL?

Som du ser, i asynkront modus, er forskjellen mellom binærprotokollen og SQL innenfor marginene for feil.
Swoole
Vi finner igjen det optimale antallet korutiner, nå for Swoole:

Vi holder oss til 25. La oss gjenta det samme triks som med Async-utvidelsen – vi fordeler 10 000 operasjoner mellom 25 korutiner. I tillegg legger vi til en ny test hvor vi deler hele jobben på 2 prosesser (det vil si hver prosess vil utføre 5 000 operasjoner i 25 korutiner). Prosessene vil bli opprettet ved hjelp av SwooleProcess.
Resultater:

Swoole viser en litt lavere ytelse sammenlignet med Async når det kjøres i én prosess, men med 2 prosesser endres bildet radikalt (tallet 2 er ikke tilfeldig, på min maskin viste 2 prosesser det beste resultatet).
Forresten, i Async-utvidelsen finnes det også et API for å jobbe med prosesser, men jeg la ikke merke til noen forskjell i ytelsen fra å kjøre benchmarkene i én eller flere prosesser (det kan være jeg gjorde noe feil).
SQL vs binærprotokoll:

Akkurat som med Async, blir forskjellen mellom binære og SQL-operasjoner utjevnet i asynkront modus.
Parallel
Siden Parallel-utvidelsen ikke handler om korutiner, men om tråder, måler vi det optimale antallet parallelle tråder:

Det er 16 på min maskin. La oss kjøre benchmarkene for kontaktene med 16 parallelle tråder:

Som du ser, er resultatet til og med bedre enn med asynkrone utvidelser (unntatt Swoole som kjører på 2 prosesser). Merk at for PECL-kontakten er det tomt ved oppdatering og upsert-operasjoner. Dette er relatert til at disse operasjonene feilet – jeg vet ikke om det er på grunn av ext-parallel, ext-tarantool eller begge.
Nå sammenligner vi ytelsen for SQL:

La oss se likheten med grafen for kontaktene som ble kjørt synkront?
Alt tilsammen
Og til slutt, la oss samle alle resultatene i én graf for å se det totale bildet for de testede utvidelsene. Vi vil legge til én ny test på grafen som vi ikke har gjort ennå ― vi vil kjøre Async-korutiner parallelt ved hjelp av Parallel*. Ideen om å integrere de nevnte utvidelsene har allerede av forfatterne, men det ble aldri oppnådd noen konsensus, så vi må gjøre det selv.
* Det ble ikke mulig å kjøre Swoole-korutiner med Parallel, disse utvidelsene ser ut til å være inkompatible.
Så, de endelige resultatene:

In conclusion
Etter min mening er resultatene ganske imponerende, og jeg er merkelig sikker på at dette ikke er slutten! Om dette er nødvendig for deg i et virkelig prosjekt, må du avgjøre selv, jeg vil bare si at for meg har dette vært et interessant eksperiment som gjør det mulig å vurdere hvor mye man kan "klemme" ut av en synkron TCP-tilkobling med minimalt med innsats. Hvis du har ideer for å forbedre benchmarkene ― vil jeg gjerne vurdere din pull request. Hele koden med instruksjoner for kjøring og resultater er publisert i en egen .
Kilde: habr.com
