
În ecosistemul PHP, în prezent există doi conectori pentru a lucra cu serverul Tarantool - este vorba despre extensia oficială PECL , scrisă în C, și , scrisă în PHP. Eu sunt autorul ultimei.
În acest articol, aș dori să împărtășesc rezultatele testării performanței ambelor biblioteci și să arăt cum, cu modificări minime în cod, se poate obține o creștere a performanței de 3-5% (pe teste sintetice!).
Ce vom testa?
Vom testa conectorii menționați anterior sincron care vor fi rulați asincron, în paralel și asincron-paralel. 🙂 De asemenea, nu dorim să modificăm codul conectorilor în sine. În prezent, există câteva extensii disponibile care permit realizarea dorită:
- ― un cadru asincron de înaltă performanță pentru PHP. Este folosit de giganți ai internetului precum Alibaba și Baidu. Începând cu versiunea 4.1.0, a apărut o metodă magică SwooleRuntime::enableCoroutine(), care permite "cu o singură linie de cod să transforme bibliotecile de rețea sincrone PHP în cele asincrone".
- Async ― până de curând o extensie promițătoare pentru lucrul asincron în PHP. De ce până de curând? Din păcate, din motive necunoscute, autorul a șters repository-ul, iar soarta ulterioară a proiectului este incertă. Va trebui să folosim dintre forks. Ca și Swoole, această extensie permite, cu un simplu gest, activarea asincronizării prin înlocuirea implementării standard a fluxurilor TCP și TLS cu versiunile lor asincrone. Aceasta se face prin opțiunea “async.tcp = 1«.
- ― o extensie destul de nouă de la binecunoscutul Joe Watkins, autor al unor biblioteci precum phpdbg, apcu, pthreads, pcov, uopz. Extensia oferă API pentru programarea multiplus și este poziționată ca o alternativă la pthreads. O limitare semnificativă a bibliotecii este că funcționează doar cu versiuni PHP ZTS (Zend Thread Safe).
Cum vom testa?
Vom rula o instanță de Tarantool cu jurnalul de scriere anticipată dezactivat (wal_mode = none) și cu un buffer de rețea mărit (readahead = 1 * 1024 * 1024). Prima opțiune va exclude lucrul cu discul, iar a doua va permite citirea mai multor cereri din bufferul sistemului de operare, minimizând astfel numărul apelurilor de sistem.
Pentru benchmark-urile care lucrează cu date (inserare, ștergere, citire etc.), înainte de a începe benchmark-ul, va fi (re)creat un spațiu memtx, în care valorile indexului primar sunt generate de un generator de valori ordonate ale numerelor întregi (sequence).
DDL-ul spațiului arată astfel:
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}})Dacă este necesar, înainte de a începe benchmark-ul, spațiul se umple cu 10,000 de tuple de tipul
{id, "tuplе_<id>"}Accesul la tupluri se face printr-o valoare aleatorie a cheii.
Benchmark-ul în sine este o singură cerere către server, care se execută de 10,000 de ori (revoluții), care, la rândul său, sunt executate în iterații. Iterațiile se repetă până când toate variațiile de timp între 5 iterații sunt în limitele de toleranță acceptabile de 3 %*. După aceea, se ia un rezultat mediat. Între iterații se face o pauză de 1 secundă, pentru a nu permite procesorului să intre în throttling. Colectorul de gunoi Lua este oprit înainte de fiecare iterație și pornit forțat după finalizarea acesteia. Procesul PHP este lansat doar cu extensiile necesare pentru benchmark, cu bufferizarea ieșirii activată și colectorul de gunoi dezactivat.
* Numărul de revoluții, iterații și pragul de toleranță pot fi modificate în setările benchmark-ului.
Mediul de testare
Rezultatele publicate mai jos au fost realizate pe un MacBookPro (2015), sistemul de operare – Fedora 30 (versiunea kernel 5.3.8-200.fc30.x86_64). Tarantool a fost lansat în Docker cu parametrul „--network host".
Versiunile pachetelor:
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 pentru 7.3)*
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-parallel: 1.1.3
* Din păcate, conectorul oficial nu funcționează cu versiunea PHP > 7.2. Pentru a compila și a rula extensia pe PHP 7.3, a fost necesar să se utilizeze .
Rezultate
Modul sincron
Protocolul Tarantool utilizează un format binar pentru serializarea mesajelor. În conectorul PECL, serializarea este ascunsă profund în adâncurile bibliotecii și influențarea procesului de codificare din codul userland . Connectorul pe PHP pur, pe de altă parte, oferă posibilitatea personalizării procesului de codare prin extinderea codificatorului standard sau prin utilizarea propriei implementări. Din cutie sunt disponibile două codificatoare, unul bazat pe (extensia oficială MessagePack PECL), celălalt - pe (pe PHP pur).
Înainte de a compara conectorii, să măsurăm performanța codificatorilor MessagePack pentru conectorul PHP și în testele ulterioare vom folosi acela care va arăta cel mai bun rezultat:

Deși versiunea PHP (Pure) este inferioară extensiei PECL în viteză, în proiectele reale, totuși, aș recomanda utilizarea acestuia , deoarece în extensia oficială MessagePack specificația formatului este implementată doar parțial (de exemplu, nu există suport pentru tipuri de date personalizate, fără de care nu veți putea utiliza Decimal - noul tip de date introdus în Tarantool 2.3) și prezintă o serie de alte (inclusiv probleme de compatibilitate cu PHP 7.4). În general, proiectul pare abandonat.
Așadar, să măsurăm performanța conectorilor în modul sincron:

După cum se vede din grafic, conectorul PECL (Tarantool) arată o performanță mai bună în comparație cu conectorul pe PHP (Client). Dar nu este de mirare, având în vedere că acesta din urmă, pe lângă faptul că este implementat într-o limbă mai lentă, efectuează, în esență, mai multă muncă: la fiecare apel se creează un nou obiect Request și Response (în cazul Select - și Criteria, iar în cazul Update/Upsert - Operations), entitățile separate Connection, Packer și Handler de asemenea, adaugă overhead. Evident, flexibilitatea are un preț. Cu toate acestea, în general, interpretatorul PHP arată o bună performanță, deși diferența există, aceasta este nesemnificativă și, probabil, va fi și mai mică cu utilizarea preloading în PHP 7.4, nemaivorbind de JIT în PHP 8.
Să continuăm. În Tarantool 2.0 a apărut suportul pentru SQL. Să încercăm să executăm operațiile Select, Insert, Update și Delete folosind protocolul SQL și să comparăm rezultatele cu echivalentele noSQL (binare):

Rezultatele SQL nu sunt foarte impresionante (rețin că încă testăm modul sincron). Totuși, nu aș vrea să mă descurajez din cauza acestui lucru prea devreme, suportul pentru SQL este încă în dezvoltare activă (recent, de exemplu, a fost adăugat suport pentru ) și, judecând după lista , motorul SQL așteaptă o serie de optimizări.
Async
Ei bine, să vedem acum cum ne poate ajuta extensia Async să îmbunătățim rezultatele de mai sus. Pentru scrierea programelor asincrone, extensia oferă un API bazat pe corutine (coroutines), pe care îl vom utiliza. Prin experiment, am constatat că numărul optim de corutine pentru mediu nostru este 25:

„Distribuim” 10.000 de operații pe 25 de corutine și vedem ce a ieșit:

Numărul de operații pe secundă a crescut de mai bine de 3 ori pentru !
Din păcate, conectorul PECL nu s-a lansat cu ext-async.
Dar ce este cu SQL?

După cum puteți vedea, în modul asincron, diferența dintre protocolul binar și SQL a intrat în marja de eroare.
Swoole
Din nou, stabilim numărul optim de corutine, acum pentru Swoole:

Ne vom opri la 25. Vom repeta aceeași manevră ca și cu extensia Async - vom distribui 10.000 de operații între 25 de corutine. În plus, vom adăuga un alt test, în care vom împărți toată munca în 2 procese (adică fiecare proces va executa 5.000 de operații în 25 de corutine). Procesele vor fi create prin intermediul SwooleProcess.
Rezultatele:

Swoole arată un rezultat ușor mai scăzut comparativ cu Async atunci când este lansat într-un singur proces, dar cu 2 procese, imaginea se schimbă radical (numărul 2 nu este ales întâmplător, pe mașina mea, exact 2 procese au dat cele mai bune rezultate).
Apropo, în extensia Async există și un API pentru lucrul cu procese, totuși, nu am observat nicio diferență în ceea ce privește rularea benchmark-urilor într-un proces sau în mai multe (nu exclud că am greșit pe undeva).
SQL vs protocolul binar:

Așa cum s-a întâmplat și cu Async, diferența dintre operațiile binare și SQL se estompează în modul asincron.
Parallel
Deoarece extensia Parallel nu este despre corutine, ci despre fire, să măsurăm numărul optim de fire paralele:

Este de 16 pe mașina mea. Să lansăm benchmark-urile conectorilor pe 16 fire paralele:

După cum puteți observa, rezultatul este chiar mai bun decât cu extensiile asincrone (fără a lua în considerare Swoole rulând pe 2 procese). Rețineți că pentru conectorul PECL, la Update și Upsert operațiile sunt goale. Aceasta se datorează faptului că aceste operații au eșuat cu o eroare - nu știu din cauza ext-parallel, ext-tarantool sau a ambelor.
Acum să comparăm performanța SQL:

Ați observat asemănarea cu graficul pentru conectorii rulați sincron?
Totul împreună
Și, în cele din urmă, să reunim toate rezultatele într-un singur grafic pentru a vedea imaginea de ansamblu a extensiilor testate. Vom adăuga pe grafic doar un nou test pe care nu l-am efectuat încă ― vom rula corutine Async în paralel folosind Parallel*. Ideea integrării extensiilor menționate mai sus a fost de autori, dar nu s-a ajuns la un consens, va trebui să facem asta singuri.
* Nu am reușit să rulez corutine Swoole cu Parallel, se pare că aceste extensii sunt incompatibile.
Așadar, rezultatele finale sunt:

În concluzie
În opinia mea, rezultatele obținute sunt destul de remarcabile și, din câte am observat, sunt de părere că acesta nu este chiar limita! Dacă aveți nevoie de asta într-un proiect real, este decizia exclusiv a dumneavoastră, pot spune doar că pentru mine a fost un experiment interesant, care permite evaluarea a cât de mult se poate „extrage” dintr-un conector TCP sincron cu un efort minim. Dacă aveți sugestii pentru îmbunătățirea benchmark-urilor, voi lua în considerare cu plăcere cererea dumneavoastră de pull. Întreg codul cu instrucțiuni de rulare și rezultate este publicat într-un .
Sursa: habr.com
