
V ekosystému PHP v současnosti existují dva konektory pro práci se serverem Tarantool ― oficiální rozšíření PECL , napsané v C, a , napsané v PHP. Jsem autorem posledního z nich.
V tomto článku bych se chtěl podělit o výsledky testování výkonu obou knihoven a ukázat, jak pomocí minimálních změn v kódu dosáhnout 3-5% nárůstu výkonu (na syntetických testech!).
Co budeme testovat?
Budeme testovat výše zmíněné synchronní konektory, spuštěné asynchronně, paralelně a asynchronně-paralelně. 🙂 Také nechceme zasahovat do kódu samotných konektorů. V tuto chvíli je k dispozici několik rozšíření, která umožňují dosáhnout požadovaného:
- ― vysoce výkonný asynchronní framework pro PHP. Používají ho internetoví giganti jako Alibaba a Baidu. Od verze 4.1.0 se objevil kouzelný metod SwooleRuntime::enableCoroutine(), který "jedním řádkem kódu přetváří synchronní síťové knihovny PHP na asynchronní".
- Async ― až donedávna velmi nadějné rozšíření pro asynchronní práci v PHP. Proč až donedávna? Bohužel z neznámého důvodu autor odstranil repozitář a další osud projektu je nejasný. Budeme muset využít z forků. Stejně jako Swoole, toto rozšíření umožňuje jedním jednoduchým tahem proměnit synchronní implementaci TCP a TLS na jejich asynchronní verze prostřednictvím volby „async.tcp = 1«.
- ― poměrně nové rozšíření od známého Joe Watkinse, autora knihoven jako phpdbg, apcu, pthreads, pcov, uopz. Toto rozšíření poskytuje API pro vícetrýskové zpracování v PHP a je prezentováno jako náhrada pthreads. Značným omezením knihovny je, že funguje pouze s verzí PHP ZTS (Zend Thread Safe).
Jak budeme testovat?
Spustíme instanci Tarantool s vypnutým protokolem předběžného zápisu (wal_mode = none) a zvýšeným síťovým bufferem (readahead = 1 * 1024 * 1024). První možnost vyloučí práci s diskem, druhá umožní číst více dotazů z cache operačního systému a tím minimalizovat počet systémových volání.
Pro benchmarky, které pracují s daty (vkládání, mazání, čtení atd.), bude před spuštěním benchmarku (pře)vytvořen memtx prostor, ve kterém jsou hodnoty primárního indexu vytvářeny generátorem uspořádaných celých čísel (sequence).
DDL prostoru vypadá 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}})Pokud je to nutné, před spuštěním benchmarku se prostor naplní 10 000 koryty ve tvaru
{id, "tuplе_"}Přístup ke korytům se provádí pomocí náhodné hodnoty klíče.
Samotný benchmark představuje jediný dotaz na server, který se provádí 10 000krát (revolucí), které jsou prováděny v iteracích. Iterace se opakují, dokud se všechny odchylky v čase mezi 5 iteracemi nenacházejí v rámci přijatelné chyby 3 %*. Po tom se vezme průměrný výsledek. Mezi iteracemi je pauza 1 sekundu, aby se procesoru nedovolilo přejít do throttlingu. Sběrač odpadu Lua je vypnut před každou iterací a je donucen spuštěn po jejím dokončení. PHP proces je spuštěn pouze s nezbytnými rozšířeními pro benchmark, s povoleným vyrovnáváním výstupu a vypnutým sběračem odpadu.
* Počet revolucí, iterací a prahová hodnota chyby mohou být změněny v nastaveních benchmarku.
Testovací prostředí
Níže zveřejněné výsledky byly získány na MacBookPro (2015), operační systém — Fedora 30 (verze jádra 5.3.8-200.fc30.x86_64). Tarantool byl spuštěn v Dockeru s parametrem "--network host".
Verze balíčků:
Tarantool: 2.3.0-115-g5ba5ed37e
Docker: 19.03.3, build a872fc2f86
PHP: 7.3.11 (cli) (postaven: 22. října 2019 08:11:04)
tarantool/client: 0.6.0
rybakit/msgpack: 0.6.1
ext-tarantool: 0.3.2 (+ patch pro 7.3)*
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-parallel: 1.1.3
* Bohužel, oficiální konektor nefunguje s verzí PHP > 7.2. Abychom kompilovali a spustili rozšíření na PHP 7.3, museli jsme využít .
Výsledky
Synchronní režim
Protokol Tarantool využívá binární formát pro serializaci zpráv. V konektoru PECL je serializace skryta hluboko v útrobách knihovny a ovlivnit proces kódování z userland kódu . Konnektor na čistém PHP naopak poskytuje možnost přizpůsobení procesu kódování rozšířením standardního kodéru nebo možností použití vlastní implementace. K dispozici jsou dva kodérů, jeden založený na (oficiální rozšíření MessagePack PECL), druhý - na (na čistém PHP).
Před porovnáním konektorů změříme výkon MessagePack kodérů pro PHP konektor a v dalších testech budeme používat ten, který ukáže nejlepší výsledek:

I když PHP verze (Pure) zaostává za rozšířením PECL v rychlosti, v reálných projektech bych přesto doporučil použít právě , protože v oficiálním rozšíření MessagePack je specifikace formátu implementována pouze částečně (například není podpora uživatelských typů dat, bez které nebudete moci použít Decimal - nový typ dat představený v Tarantool 2.3) a má řadu dalších (včetně problémů s kompatibilitou s PHP 7.4). Celkově projekt vypadá opuštěně.
Takže změříme výkon konektorů v synchronním režimu:

Jak je vidět z grafu, konektor PECL (Tarantool) vykazuje lepší výkon než konektor na PHP (Client). Ale to není překvapivé, vezmeme-li v úvahu, že poslední, kromě toho, že je implementován v pomalejším jazyce, vykonává v podstatě více práce: při každém volání se vytváří nový objekt Request a Odpověď (v případě Select - ještě i Criteria, a v případě Update/Upsert - Operations), jednotlivé entity Connection, Packer a Handler také přidávají overhead. Je zřejmé, že za flexibilitu se platí. Nicméně, celkově PHP interpret vykazuje dobrý výkon, i když rozdíl je a není zanedbatelný, ale možná bude ještě menší při použití preloading v PHP 7.4, nemluvě o JIT v PHP 8.
Pokračujeme dále. V Tarantool 2.0 byla přidána podpora SQL. Zkusíme provést operace Select, Insert, Update a Delete pomocí SQL protokolu a porovnat výsledky s noSQL (binárními) ekvivalenty:

Výsledky SQL nejsou příliš ohromující (připomínám, že stále testujeme synchronní režim). Nicméně, nenechal bych se tímto trápit předčasně, podpora SQL je stále v aktivním vývoji (nedávno například přibyla podpora ) a podle seznamu , SQL engine v budoucnu čeká řada optimalizací.
Asynchronní
Nuže, podívejme se nyní, jak rozšíření Asynchronní může pomoci zlepšit výše uvedené výsledky. Pro psaní asynchronních programů rozšíření poskytuje API založené na korutinách (coroutines), jichž využijeme. Zjistili jsme, že optimální počet korutin pro naše prostředí je 25:

„Rozložíme“ 10 000 operací mezi 25 korutin a podíváme se, co jsme získali:

Počet operací za sekundu se zvýšil více než třikrát pro !
Bohužel, PECL konektor se nespustil s ext-async.
A co SQL?

Jak vidíte, v asynchronním režimu je rozdíl mezi binárním protokolem a SQL v mezích chybovosti.
Swoole
Opět zjišťujeme optimální počet korutin, tentokrát pro Swoole:

Zůstaneme u čísla 25. Opakujeme stejný trik jako s rozšířením Asynchronní – rozdělíme 10 000 operací mezi 25 korutin. Kromě toho přidáme další test, ve kterém rozdělíme veškerou práci na 2 procesy (to znamená, že každý proces provede 5 000 operací v 25 korutinách). Procesy budou vytvářeny pomocí SwooleProcess.
Výsledky:

Swoole vykazuje mírně nižší výsledek ve srovnání s Asynchronním při spuštění v jednom procesu, ale s 2 procesy se obraz radikálně mění (číslo 2 bylo vybráno nikoli náhodou, na mém stroji skutečně 2 procesy ukázaly nejlepší výsledek).
Mimochodem, v rozšíření Asynchronní je také API pro práci s procesy, nicméně jsem tam nezaznamenal žádný rozdíl mezi spuštěním benchmarků v jednom či více procesech (není vyloučeno, že jsem někde udělal chybu).
SQL vs binární protokol:

Stejně jako u Asynchronního, rozdíl mezi binárními a SQL operacemi se v asynchronním režimu vyrovnává.
Parallel
Protože rozšíření Paralelní není o korutinách, ale o vláknech, změříme optimální počet paralelních vláken:

Ten je na mém stroji 16. Spustíme benchmarky konektorů na 16 paralelních vláknech:

Jak vidíte, výsledek je dokonce lepší než s asynchronními rozšířeními (nepočítaje Swoole spuštěném na 2 procesech). Všimněte si, že pro konektor PECL na místě Update a Upsert operací je prázdno. To je způsobeno tím, že tyto operace selhaly s chybou – nevím, jestli je to vinou ext-parallel, ext-tarantool nebo obou.
Nyní porovnáme výkon SQL:

Všimli jste si podobnosti s grafem pro konektory spuštěné synchronně?
Vše dohromady
A nakonec shrneme všechny výsledky do jednoho grafu, abychom viděli celkový obrázek pro testovaná rozšíření. Na graf přidáme pouze jeden nový test, který jsme ještě nedělali — spustíme korutiny Async paralelně pomocí Parallel*. Myšlenka integrace výše zmíněných rozšíření již autory, avšak konsenzus nebyl dosažen, bude to muset udělat sám.
* Spustit korutiny Swoole s Parallel se nepodařilo, zdá se, že tato rozšíření nejsou kompatibilní.
Takže finální výsledky:

Na závěr
Podle mě jsou výsledky velmi slušné a nějakým způsobem jsem si jistý, že to ještě není konec! Je na vás, zda to potřebujete ve skutečném projektu, řeknu jen, že pro mě to byl zajímavý experiment, který umožnil posoudit, kolik lze "vytěžit" ze synchronního TCP konektoru s minimálními námahy. Pokud máte nějaké nápady na zlepšení benchmarků — rád se podívám na váš pull request. Celý kód s instrukcemi k spuštění a výsledky je publikován v samostatném .
Zdroj: habr.com
