
V ekosystéme PHP v súčasnosti existujú dve rozhrania na prácu so serverom Tarantool - oficiálne rozšírenie PECL , napísané v C, a , napísané v PHP. Som autorom toho druhého.
V tomto článku by som sa chcel podeliť o výsledky testovania výkonu oboch knižníc a ukázať, ako môžeme dosiahnuť 3-5% zlepšenie výkonu pri minimálnych zmenách v kóde (na syntetických testoch!).
Čo budeme testovať?
Budeme testovať vyššie spomenuté synchronné rozhrania, spustené asynchrónne, paralelne a asynchrónne-paralelne. 🙂 Taktiež sa nechceme dotýkať kódu samotných rozhraní. V súčasnosti je k dispozícii niekoľko rozšírení, ktoré umožňujú dosiahnuť požadovaný výsledok:
- ― vysoce výkonný asynchrónny rámec pre PHP. Používa ho niekoľko internetových gigantov ako Alibaba a Baidu. Od verzie 4.1.0 sa objavila čarovná metóda SwooleRuntime::enableCoroutine(), ktorá "jedným riadkom kódu pretransformuje synchronné sieťové knižnice PHP na asynchrónne".
- Async ― donedávna veľmi perspektívne rozšírenie pre asynchrónnu prácu v PHP. Prečo donedávna? Bohužiaľ, z neznámeho dôvodu autor odstránil repozitár a budúcnosť projektu je nejasná. Budeme musieť využiť z forkov. Rovnako ako Swoole, toto rozšírenie umožňuje jednoduchým spôsobom premeniť synchronnosť na asynchrónnosť výmenou štandardnej implementácie TCP a TLS prúdov za ich asynchrónne verzie. Robí sa to cez možnosť „async.tcp = 1«.
- ― pomerne nové rozšírenie od známeho Joea Watkinsa, autora takých knižníc ako phpdbg, apcu, pthreads, pcov, uopz. Rozšírenie poskytuje API pre viaceré vlákna v PHP a je poziciované ako náhrada za pthreads. Významným obmedzením knižnice je, že funguje len s verziou PHP ZTS (Zend Thread Safe).
Ako budeme testovať?
Spustíme inštanciu Tarantoolu s vypnutým žurnálovým predzápisom (wal_mode = none) a zvýšeným sieťovým bufferom (readahead = 1 * 1024 * 1024). Prvá možnosť vylúči prácu s diskom, druhá umožní čítať viac požiadaviek z bufferu operačného systému a tým minimalizovať počet systémových volaní.
Pre benchmarkov, ktoré pracujú s údajmi (vkladanie, odstraňovanie, čítanie atď.), sa pred spustením benchmarku vytvorí (alebo znovu vytvorí) memtx-špeciál, v ktorom sa hodnoty primárneho indexu generujú generátorom usporiadaných hodnôt celých čísel (sequence).
DDL špeciálu vyzerá takto:
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}})Ak je to potrebné, pred spustením benchmarku sa špeciál naplní 10 000 kópiami typu
{id, "tuplе_<id>"}Prístup k kópiám je zabezpečený pomocou náhodnej hodnoty kľúča.
Sám benchmark predstavuje jednotkový dotaz na server, ktorý sa vykonáva 10 000-krát (revolúcií), ktoré sa vykonávajú v iteráciách. Iterácie sa opakujú, kým všetky odchýlky v čase medzi 5 iteráciami nebudú v rámci prípustnej chyby 3 %*. Po tom sa vezme priemerný výsledok. Medzi iteráciami je pauza 1 sekunda, aby procesor neprešiel do režimu zníženia výkonu. Zbieranie odpadu Lua je vypnuté pred každou iteráciou a nútene spustené po jej dokončení. PHP proces sa spúšťa iba s potrebnými rozšíreniami pre benchmark, s povoleným vyrovnávacím pamäťou a vypnutým zbieracím mechanizmom.
* Počet revolúcií, iterácií a prahová chyba je možné zmeniť v nastaveniach benchmarku.
Testovacie prostredie
Nižšie uvedené výsledky boli získané na MacBookPro (2015), operačný systém — Fedora 30 (verzia jadra 5.3.8-200.fc30.x86_64). Tarantool bol spustený v Dockeri s parametrom "--network host".
Verzie balíkov:
Tarantool: 2.3.0-115-g5ba5ed37e
Docker: 19.03.3, build a872fc2f86
PHP: 7.3.11 (cli) (postavené: 22. októbra 2019 08:11:04)
tarantool/client: 0.6.0
rybakit/msgpack: 0.6.1
ext-tarantool: 0.3.2 (+ oprava pre 7.3)*
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-parallel: 1.1.3
* Žiaľ, oficiálny konektor nefunguje s verziou PHP > 7.2. Aby som skompiloval a spustil rozšírenie na PHP 7.3, musel som použiť .
Výsledky
Súčasný režim
Protokol Tarantool používa binárny formát na serializáciu správ. V konektore PECL je serializácia skrytá hlboko v vrstve knižnice a ovplyvniť proces kódovania z userland-kódu Konektor na čisto PHP poskytuje možnosť prispôsobenia procesu kódovania rozšírením štandardného kódovača alebo možnosťou použitia vlastnej implementácie. Z krabice sú k dispozícii dvaja kódovače, jeden založený na (oficiálne rozšírenie MessagePack PECL), druhý — na (na čisto PHP).
Pred porovnaním konektorov zmerajme výkon MessagePack kódovačov pre PHP konektor a v ďalších testoch budeme používať ten, ktorý ukáže najlepší výsledok:

Hoci PHP verzia (Pure) zaostáva rozšíreniu PECL z hľadiska rýchlosti, v reálnych projektoch by som aj tak odporúčal používať práve , pretože v oficiálnom rozšírení MessagePack je špecifikácia formátu implementovaná iba čiastočne (napríklad neexistuje podpora používateľských typov údajov, bez ktorých nebudete môcť použiť Decimal — nový typ údajov, predstavený v Tarantool 2.3) a má niekoľko ďalších (vrátane problémov s kompatibilitou s PHP 7.4). A celkovo projekt vyzerá zanedbane.
Takže zmerajme výkon konektorov v synchronnom režime:

Ako je vidieť z grafu, konektor PECL (Tarantool) ukazuje lepší výkon v porovnaní s konektorom na PHP (Client). Ale to nie je prekvapujúce, vzhľadom na to, že posledný, okrem toho, že je implementovaný na pomalšom jazyku, vykonáva v podstate viac práce: pri každom volaní sa vytvára nový objekt Požiadavka a Response (v prípade Select — ešte aj Criteria, a v prípade Update/Upsert — Operations), samostatné entity Pripojenie, Packer a Handler tiež pridávajú overhead. Je zrejmé, že za flexibilitu sa platí. Avšak, celkovo PHP interpretátor ukazuje dobrý výkon, aj keď rozdiel existuje, je nevýznamný a možno bude ešte menší pri použití preloading v PHP 7.4, nehovoriac už o JIT v PHP 8.
Posúvame sa ďalej. V Tarantool 2.0 sa objavila podpora SQL. Skúsme vykonať operácie Select, Insert, Update a Delete pomocou SQL protokolu a porovnať výsledky s noSQL (binárnymi) ekvivalentmi:

Výsledky SQL nie sú veľmi pôsobivé (pripomínam, že stále testujeme synchronný režim). Avšak, nemal by som byť z toho znepokojený predčasne, podpora SQL je stále v aktívnom vývoji (relatívne nedávno, napríklad, sa pridala podpora ) a, súdiac podľa zoznamu , SQL engine čaká na množstvo optimalizácií.
Async
No, pozrime sa, ako rozšírenie Async môže pomôcť zlepšiť naše výsledky vyššie. Na písanie asynchrónnych programov rozšírenie poskytuje API založené na korutinách (coroutines), ktoré využijeme. Skúsenostne zistíme, že optimálny počet korutín pre naše prostredie je 25:

Rozdeľujeme 10,000 operácií medzi 25 korutín a sledujeme, čo to prinieslo:

Počet operácií za sekundu vzrástol viac ako trojnásobne pre !
Žiaľ, konektor PECL sa nespustil s ext-async.
A čo SQL?

Ako vidíte, v asynchrónnom režime sa rozdiel medzi binárnym protokolom a SQL dostal do medzí nepresnosti.
Swoole
Opäť zisťujeme optimálny počet korutín, tentokrát pre Swoole:

Zostaneme na 25. Opakujeme ten istý trik ako s rozšírením Async – rozdelíme 10,000 operácií medzi 25 korutín. Okrem toho pridáme ďalší test, v ktorom rozdelíme všetku prácu na 2 procesy (teda každý proces vykoná 5,000 operácií v 25 korutínach). Procesy budú vytvorené pomocou SwooleProcess.
Výsledky:

Swole ukazuje o niečo nižší výsledok v porovnaní s Async pri spustení v jednom procese, ale pri 2 procesoch sa situácia radikálne mení (číslo 2 bolo vybrané nie náhodou, na mojom zariadení práve 2 procesy ukázali najlepší výsledok).
Mimochodom, v rozšírení Async je tiež API na prácu s procesmi, ale tam som si nevšimol žiadny rozdiel medzi spustením benchmarkingov v jednom alebo viacerých procesoch (nie je vylúčené, že som niekde urobil chybu).
SQL vs binárny protokol:

Rovnako ako s Async, rozdiel medzi binárnymi a SQL operáciami sa vyrovnáva v asynchrónnom režime.
Parallel
Keďže rozšírenie Parallel nie je o korutinách, ale o vláknach, odmerajme optimálny počet paralelných vlákien:

Ten je 16 na môjho zariadení. Spustíme benchmarky konektorov na 16 paralelných vláknach:

Ako vidíte, výsledok je dokonca lepší ako s asynchrónnymi rozšíreniami (nepočítajúc Swoole spustené na 2 procesoch). Všimnite si, že pre konektor PECL je na mieste operácií Update a Upsert prázdno. To súvisí s tým, že tieto operácie spadli s chybou – neviem, či za to môže ext-parallel, ext-tarantool, alebo oboje.
Teraz porovnajme výkon SQL:

Všimli ste si podobnosť s grafom pre konektory spustené synchronne?
Všetko dokopy
A nakoniec zhrnieme všetky výsledky do jedného grafu, aby sme videli celkový obraz testovaných rozšírení. Na graf pridáme len jeden nový test, ktorý sme ešte nerobili — spustíme Async korutiny paralelne pomocou Parallel*. Myšlienka integrácie vyššie spomenutých rozšírení už autormi, avšak konsenzus nebol dosiahnutý, budeme musieť spraviť to sami.
* Spustiť Swoole korutiny s Parallel sa nepodarilo, zdá sa, že tieto rozšírenia nie sú kompatibilné.
Takže, finálne výsledky:

Na záver
Myslím, že výsledky sú celkom potešujúce, a neviem prečo, ale som presvedčený, že to ešte nie je konečný limit! Či to potrebujete vo svojom skutočnom projekte, rozhodnete sa sami, poviem len, že pre mňa to bol zaujímavý experiment, ktorý umožnil posúdiť, koľko sa dá „vytiahnuť“ zo synchronného TCP konektora s minimálnym úsilím. Ak máte nápady na vylepšenie benchmarkov, rád posúdim váš pull request. Celý kód s pokynmi na spustenie a výsledkami je publikovaný v samostatnom .
Zdroj: habr.com
