Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel

W ekosystemie PHP dostępne są obecnie dwa konektory do współpracy z serwerem Tarantool - jest to oficjalne rozszerzenie PECL tarantool/tarantool-php, napisany w C i tarantool-php/klient, napisany w PHP. Jestem autorem tego ostatniego.

W tym artykule chciałbym podzielić się wynikami testów wydajnościowych obu bibliotek i pokazać, jak przy minimalnych zmianach w kodzie można osiągnąć wzrost wydajności o 3-5 (na testach syntetycznych!).

Co będziemy testować?

Przetestujemy te wymienione powyżej synchroniczny złącza działające asynchronicznie, równolegle i asynchronicznie-równolegle. 🙂 Nie chcemy też ingerować w kod samych złączy. Obecnie dostępnych jest kilka rozszerzeń, dzięki którym możesz osiągnąć to, czego chcesz:

  • Wełna ― wysokowydajny asynchroniczny framework dla PHP. Używany przez takich gigantów internetowych jak Alibaba i Baidu. Od wersji 4.1.0 pojawiła się magiczna metoda SwooleRuntime::enableCoroutine(), która pozwala „konwertować synchroniczne biblioteki sieciowe PHP na asynchroniczne za pomocą jednej linii kodu”.
  • Async był do niedawna bardzo obiecującym rozszerzeniem do asynchronicznej pracy w PHP. Dlaczego do niedawna? Niestety z nieznanego mi powodu autor usunął repozytorium i dalsze losy projektu nie są jasne. Będę musiał to wykorzystać jeden z widelców. Podobnie jak Swoole, to rozszerzenie pozwala łatwo włączyć spodnie jednym ruchem nadgarstka, aby włączyć asynchronię, zastępując standardową implementację strumieni TCP i TLS ich wersjami asynchronicznymi. Odbywa się to poprzez opcję „async.tcp = 1".
  • Parallel – całkiem nowe rozszerzenie od znanego Joe Watkinsa, autora takich bibliotek jak phpdbg, apcu, pthreads, pcov, uopz. Rozszerzenie zapewnia API do wielowątkowości w PHP i jest pozycjonowane jako zamiennik pthreads. Istotnym ograniczeniem biblioteki jest to, że działa ona tylko z wersją PHP ZTS (Zend Thread Safe).

Jak będziemy testować?

Uruchommy instancję Tarantool z wyłączonym rejestrowaniem z wyprzedzeniem (wal_mode = brak) i zwiększony bufor sieciowy (odczyt z wyprzedzeniem = 1 * 1024 * 1024). Pierwsza opcja wyeliminuje pracę z dyskiem, druga umożliwi odczytanie większej liczby żądań z bufora systemu operacyjnego i tym samym zminimalizowanie liczby wywołań systemowych.

W przypadku benchmarków pracujących z danymi (wstawianie, usuwanie, odczyt itp.) przed uruchomieniem benchmarku zostanie (od)utworzona przestrzeń memtx, w której wartości indeksu pierwotnego tworzone są przez generator uporządkowanych wartości całkowitych ​(sekwencja).
Przestrzeń DDL wygląda następująco:

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}})

W razie potrzeby przed uruchomieniem benchmarku przestrzeń wypełniana jest 10,000 XNUMX krotek postaci

{id, "tuplе_<id>"}

Dostęp do krotek uzyskuje się przy użyciu losowej wartości klucza.

Sam benchmark to pojedyncze żądanie skierowane do serwera, które jest wykonywane 10,000 5 razy (obrotów), które z kolei wykonywane są w iteracjach. Iteracje powtarza się, aż wszystkie odchylenia czasowe pomiędzy 3 iteracjami będą mieściły się w akceptowalnym błędzie 1%*. Następnie pobierany jest średni wynik. Pomiędzy iteracjami występuje XNUMX-sekundowa przerwa, aby zapobiec dławieniu procesora. Moduł zbierający elementy bezużyteczne Lua jest wyłączany przed każdą iteracją i zmuszony jest do uruchomienia po jej zakończeniu. Proces PHP jest uruchamiany tylko z rozszerzeniami niezbędnymi do wykonania testu porównawczego, z włączonym buforowaniem wyjściowym i wyłączonym modułem zbierającym elementy bezużyteczne.

* Liczbę obrotów, iteracji i próg błędu można zmienić w ustawieniach testu porównawczego.

Środowisko testowe

Wyniki opublikowane poniżej zostały wykonane na MacBooku Pro (2015) z systemem operacyjnym Fedora 30 (wersja jądra 5.3.8-200.fc30.x86_64). Tarantool został uruchomiony w oknie dokowanym z parametrem „--network host".

Wersje pakietów:

Tarantool: 2.3.0-115-g5ba5ed37e
Doker: 19.03.3, kompilacja a872fc2f86
PHP: 7.3.11 (cli) (zbudowano: 22 października 2019 08:11:04)
tarantool/klient: 0.6.0
rybakit/msgpack: 0.6.1
ext-tarantool: 0.3.2 (+ poprawka dla 7.3)*
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-równolegle: 1.1.3

* Niestety oficjalny konektor nie działa z wersją PHP > 7.2. Aby skompilować i uruchomić rozszerzenie w PHP 7.3, musiałem użyć skrawek.

wyniki

Tryb synchroniczny

Protokół Tarantool wykorzystuje format binarny Pakiet wiadomości serializować wiadomości. W złączu PECL serializacja ukryta jest głęboko w głębi biblioteki i wpływa na proces kodowania z kodu użytkownika nie wydaje się możliwe. Z kolei czysty konektor PHP zapewnia możliwość dostosowania procesu kodowania poprzez rozszerzenie standardowego kodera lub wykorzystanie własnej implementacji. Dostępne są dwa enkodery, z których jeden jest oparty na msgpack/msgpack-php (oficjalne rozszerzenie MessagePack PECL), drugie jest włączone rybakit/msgpack (w czystym PHP).

Przed porównaniem konektorów zmierzymy wydajność koderów MessagePack dla konektora PHP i w dalszych testach skorzystamy z tego, który wykazuje najlepszy wynik:

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
Chociaż wersja PHP (Pure) jest gorsza od rozszerzenia PECL pod względem szybkości, w rzeczywistych projektach nadal zalecałbym jej używanie rybakit/msgpack, ponieważ w oficjalnym rozszerzeniu MessagePack specyfikacja formatu jest zaimplementowana tylko częściowo (przykładowo nie ma obsługi niestandardowych typów danych, bez których nie będzie można korzystać z Decimal – nowego typu danych wprowadzonego w Tarantool 2.3) i posiada liczba innych problemy (w tym problemy ze zgodnością z PHP 7.4). No cóż, ogólnie projekt wygląda na porzucony.

Zmierzmy więc wydajność złączy w trybie synchronicznym:

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
Jak widać na wykresie, złącze PECL (Tarantool) wykazuje lepszą wydajność w porównaniu ze złączem PHP (Client). Nie jest to jednak zaskakujące, biorąc pod uwagę, że ten ostatni, oprócz tego, że jest zaimplementowany w wolniejszym języku, faktycznie wykonuje więcej pracy: przy każdym wywołaniu tworzony jest nowy obiekt PROŚBA и Odpowiedź (w przypadku Select - także kryteria, a w przypadku aktualizacji/upsert ― operacje), oddzielne byty Statystyki z konta, Pakowacz и Treser dodają również koszty ogólne. Oczywiście elastyczność ma swoją cenę. Jednak ogólnie interpreter PHP wykazuje dobrą wydajność, chociaż jest różnica, jest ona nieznaczna, a być może będzie jeszcze mniejsza przy użyciu wstępnego ładowania w PHP 7.4, nie mówiąc już o JIT w PHP 8.

Przejdźmy dalej. Tarantool 2.0 dodał obsługę SQL. Spróbujmy wykonać operacje Select, Insert, Update i Delete przy użyciu protokołu SQL i porównać wyniki z odpowiednikami noSQL (binarnymi):

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
Wyniki SQL nie są zbyt imponujące (przypomnę, że wciąż testujemy tryb synchroniczny). Jednak nie denerwowałbym się tym z góry; obsługa SQL jest wciąż w fazie aktywnego rozwoju (na przykład stosunkowo niedawno dodano obsługę przygotowanych oświadczeń) i sądząc po liście problemy, silnik SQL zostanie w przyszłości poddany szeregowi optymalizacji.

Asynchronizacja

Cóż, teraz zobaczmy, jak rozszerzenie Async może pomóc nam poprawić powyższe wyniki. Do pisania programów asynchronicznych rozszerzenie udostępnia API oparte na współprogramach, z którego będziemy korzystać. Dowiadujemy się empirycznie, że optymalna liczba współprogramów dla naszego środowiska wynosi 25:

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
„Rozłóż” 10,000 25 operacji na XNUMX współprogramów i zobacz, co się stanie:

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
Liczba operacji na sekundę wzrosła ponad 3-krotnie tarantool-php/klient!

Niestety złącze PECL nie zaczęło się od ext-async.

A co z SQLem?

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
Jak widać, w trybie asynchronicznym różnica pomiędzy protokołem binarnym a SQL mieściła się w granicach błędu.

Wełna

Ponownie znajdujemy optymalną liczbę współprogramów, tym razem dla Swoole'a:
Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
Zatrzymajmy się na 25. Powtórzmy tę samą sztuczkę, co w przypadku rozszerzenia Async - rozdziel 10,000 25 operacji pomiędzy 2 współprogramów. Dodatkowo dodamy kolejny test, w którym całą pracę podzielimy na 5,000 dwa procesy (czyli każdy proces wykona 25 operacji w XNUMX współprogramach). Procesy zostaną utworzone przy użyciu Proces Swoole'a.

Wyniki:

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
Swole wykazuje nieco niższy wynik w porównaniu do Async, gdy jest uruchomiony w jednym procesie, ale przy 2 procesach obraz zmienia się diametralnie (liczba 2 nie została wybrana przypadkowo; na moim komputerze to 2 procesy wykazały najlepszy wynik).

Swoją drogą rozszerzenie Async ma również API do pracy z procesami, ale tam nie zauważyłem żadnej różnicy w stosunku do uruchamiania benchmarków w jednym lub większej liczbie procesów (możliwe, że gdzieś coś namieszałem).

SQL a protokół binarny:

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
Podobnie jak w przypadku Async, różnica między operacjami binarnymi i SQL jest eliminowana w trybie asynchronicznym.

Parallel

Ponieważ rozszerzenie równoległe nie dotyczy współprogramów, ale wątków, zmierzmy optymalną liczbę równoległych wątków:

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
Na moim komputerze jest to 16. Przeprowadźmy testy porównawcze łączników w 16 równoległych wątkach:

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
Jak widać, wynik jest nawet lepszy niż w przypadku rozszerzeń asynchronicznych (nie licząc Swoole działającego na 2 procesach). Należy pamiętać, że w przypadku złącza PECL operacje Update i Upsert są puste. Dzieje się tak dlatego, że te operacje zakończyły się błędem - nie wiem, czy była to wina ext-parallel, ext-tarantool, czy obu.

Porównajmy teraz wydajność SQL:

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel
Czy zauważyłeś podobieństwo do wykresu dla złączy działających synchronicznie?

Razem

Na koniec podsumujmy wszystkie wyniki na jednym wykresie, aby zobaczyć ogólny obraz testowanych rozszerzeń. Dodajmy do wykresu tylko jeden nowy test, którego jeszcze nie zrobiliśmy - uruchommy współprogramy Async równolegle, używając Parallel*. Pomysł integracji powyższych rozszerzeń już jest omówione autorów, ale nie osiągnięto konsensusu, będziesz musiał to zrobić sam.

* Nie było możliwe uruchomienie współprogramów Swoole z Parallel; wydaje się, że te rozszerzenia są niekompatybilne.

A więc ostateczne wyniki:

Przyspieszanie konektorów PHP dla Tarantool przy użyciu Async, Swoole i Parallel

Zamiast zawierania

Moim zdaniem wyniki okazały się całkiem przyzwoite i z jakiegoś powodu jestem pewien, że to nie jest limit! Niezależnie od tego, czy w prawdziwym projekcie trzeba o tym decydować wyłącznie dla siebie, powiem tylko, że dla mnie był to ciekawy eksperyment, który pozwala ocenić, ile można „wycisnąć” z synchronicznego złącza TCP przy minimalnym wysiłku. Jeśli masz pomysły na ulepszenie testów porównawczych, z przyjemnością rozważę Twoją prośbę o wycofanie. Cały kod z instrukcjami uruchamiania i wynikami jest publikowany w osobnym wydaniu repozytoria.

Źródło: www.habr.com

Dodaj komentarz