Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel

In der PHP-Ökosystem gibt es derzeit zwei Connectoren für die Arbeit mit dem Tarantool-Server – dies sind die offizielle PECL-Erweiterung tarantool/tarantool-php, geschrieben in C, und tarantool-php/client, geschrieben in PHP. Ich bin der Autor letzterer.

In diesem Artikel möchte ich die Testergebnisse von beiden Bibliotheken teilen und zeigen, wie man mit minimalen Änderungen im Code eine Leistungssteigerung von 3-5 % erreichen kann (bei synthetischen Tests!).

Was werden wir testen?

Wir werden die oben genannten synchronen Connectoren testen, die asynchron, parallel und asynchron-parallel ausgeführt werden. 🙂 Außerdem möchten wir den Code der Connectoren selbst nicht berühren. Derzeit stehen mehrere Erweiterungen zur Verfügung, die das gewünschte Ziel erreichen:

  • Swoole – ein leistungsstarker asynchroner Framework für PHP. Wird von Internet-Giganten wie Alibaba und Baidu verwendet. Seit Version 4.1.0 gibt es die magische Methode SwooleRuntime::enableCoroutine(), die es ermöglicht, "mit einer einzigen Codezeile synchronen Netzwerkbibliotheken in PHP asynchrone zu verwandeln".
  • Async – bis vor kurzem eine vielversprechende Erweiterung für asynchrone Arbeiten in PHP. Warum bis vor kurzem? Leider hat der Autor aus mir unbekannten Gründen das Repository gelöscht und die weitere Zukunft des Projekts ist ungewiss. Wir müssen auf eine der Forks zurückgreifen. Wie Swoole ermöglicht diese Erweiterung mit Leichtigkeit, die Asynchronität zu aktivieren, indem die Standardimplementierung der TCP- und TLS-Streams durch ihre asynchronen Versionen ersetzt wird. Dies erfolgt über die Option "async.tcp = 1«.
  • Parallel – eine relativ neue Erweiterung vom nicht unbekannten Joe Watkins, dem Autor solcher Bibliotheken wie phpdbg, apcu, pthreads, pcov, uopz. Die Erweiterung bietet eine API für die Multithread-Arbeit in PHP und positioniert sich als Ersatz für pthreads. Eine wesentliche Einschränkung der Bibliothek ist, dass sie nur mit der ZTS (Zend Thread Safe) Version von PHP funktioniert.

Wie werden wir testen?

Wir werden eine Tarantool-Instanz ohne Aktivierung des Write-Ahead-Logs starten (wal_mode = none) und mit einem erhöhten Netzwerkpuffer (readahead = 1 * 1024 * 1024). Die erste Option schließt die Arbeit mit der Festplatte aus, die zweite ermöglicht es, mehr Anfragen aus dem Betriebssystempuffer zu lesen und damit die Anzahl der Systemaufrufe zu minimieren.

Für Benchmarks, die mit Daten arbeiten (Einfügen, Löschen, Lesen usw.), wird vor Beginn des Benchmarks ein memtx-Space erstellt oder (wieder)hergestellt, in dem die Werte des Primärindexes durch einen Generator fortlaufender Ganzzahlen (Sequenz) erzeugt werden.
Das DDL des Spaces sieht folgendermaßen aus:

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

Falls erforderlich, wird der Space vor dem Start des Benchmarks mit 10.000 Tupeln der Form

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

Der Zugriff auf die Tupel erfolgt über einen zufälligen Schlüsselwert.

Der Benchmark selbst ist eine einzelne Anfrage an den Server, die 10.000 Mal (Revolutionen) ausgeführt wird, die wiederum in Iterationen ausgeführt werden. Die Iterationen werden wiederholt, bis alle Abweichungen in der Zeit zwischen 5 Iterationen innerhalb einer zulässigen Toleranz von 3 %* liegen. Danach wird ein Durchschnittswert genommen. Zwischen den Iterationen gibt es eine Pause von 1 Sekunde, um zu verhindern, dass der Prozessor ins Throttling übergeht. Der Lua-Garbage-Collector wird vor jeder Iteration deaktiviert und nach deren Abschluss gezwungen gestartet. Der PHP-Prozess wird nur mit den für den Benchmark erforderlichen Erweiterungen gestartet, die Ausgabe-Pufferung ist aktiviert und der Garbage-Collector ist deaktiviert.

* Die Anzahl der Revolutionen, Iterationen und die Toleranzgrenze können in den Einstellungen des Benchmarks geändert werden.

Testumgebung

Die unten veröffentlichten Ergebnisse wurden auf einem MacBook Pro (2015) erstellt, Betriebssystem — Fedora 30 (Kernelversion 5.3.8-200.fc30.x86_64). Tarantool wurde in Docker mit dem Parameter "--network host".

Paketversionen:

Tarantool: 2.3.0-115-g5ba5ed37e
Docker: 19.03.3, Build a872fc2f86
PHP: 7.3.11 (cli) (gebaut: 22. Okt 2019 08:11:04)
tarantool/client: 0.6.0
rybakit/msgpack: 0.6.1
ext-tarantool: 0.3.2 (+ Patch für 7.3)*
ext-msgpack: 2.0.3
ext-async: 0.3.0-8c1da46
ext-swoole: 4.4.12
ext-parallel: 1.1.3

* Leider funktioniert der offizielle Connector nicht mit PHP-Versionen > 7.2. Um die Erweiterung für PHP 7.3 zu kompilieren und auszuführen, musste ein Patch.

Ergebnisse

Synchroner Modus

Das Protokoll von Tarantool verwendet ein Binärformat MessagePack zur Serialisierung von Nachrichten. Im PECL-Connector ist die Serialisierung tief in den Untiefen der Bibliothek verborgen und es ist nicht möglich, den Kodierungsprozess aus dem Userland-Code zu beeinflussen.. Der Connector in reinem PHP bietet im Gegensatz dazu die Möglichkeit, den Kodierungsprozess durch die Erweiterung des Standardkodierers oder die Nutzung eigener Implementierungen anzupassen. Aus der Box sind zwei Kodierer verfügbar, einer basiert auf msgpack/msgpack-php (offizielle MessagePack PECL-Erweiterung), der andere - auf rybakit/msgpack (in reinem PHP).

Vor dem Vergleich der Connectoren messen wir die Leistung der MessagePack-Kodierer für den PHP-Connector und verwenden in den weiteren Tests den, der das beste Ergebnis zeigt:

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Obwohl die PHP-Version (Pure) in der Geschwindigkeit hinter der PECL-Erweiterung zurückbleibt, würde ich in realen Projekten dennoch empfehlen, genau diese zu verwenden, rybakit/msgpack, da die offizielle MessagePack-Erweiterung die Spezifikation des Formats nur teilweise implementiert (zum Beispiel gibt es keine Unterstützung für benutzerdefinierte Datentypen, ohne die Sie Decimal - den neuen Datentyp, der in Tarantool 2.3 eingeführt wurde, nicht verwenden können) und hat eine Reihe anderer der Probleme (einschließlich Kompatibilitätsprobleme mit PHP 7.4). Insgesamt wirkt das Projekt jedoch vernachlässigt.

Lassen Sie uns daher die Leistung der Connectoren im synchronen Modus messen:

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Wie aus dem Diagramm zu sehen ist, zeigt der PECL-Connector (Tarantool) eine bessere Leistung im Vergleich zum PHP-Connector (Client). Aber das ist auch nicht überraschend, da letzterer, abgesehen davon, dass er in einer langsameren Sprache implementiert ist, im Grunde genommen mehr Arbeit erledigt: Bei jedem Aufruf wird ein neues Objekt erstellt Request und Response (im Fall von Select - auch noch Criteria, und im Fall von Update/Upsert - Operations), separate Entitäten Verbindung, Packer und Sie fügen mit Hilfe der Handler erhöhen ebenfalls die Overhead-Kosten. Offensichtlich muss man für Flexibilität bezahlen. Insgesamt zeigt der PHP-Interpreter jedoch eine gute Leistung, auch wenn es Unterschiede gibt, sind diese gering und werden mit der Nutzung von Preloading in PHP 7.4 wahrscheinlich noch geringer sein, ganz zu schweigen vom JIT in PHP 8.

Lassen Sie uns weitermachen. In Tarantool 2.0 wurde SQL-Unterstützung eingeführt. Lassen Sie uns versuchen, die Operationen Select, Insert, Update und Delete unter Verwendung des SQL-Protokolls auszuführen und die Ergebnisse mit den noSQL (binären) Äquivalenten zu vergleichen:

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Die SQL-Ergebnisse sind nicht besonders beeindruckend (ich erinnere daran, dass wir immer noch den synchronen Modus testen). Ich würde mich jedoch nicht zu früh darüber aufregen, denn die SQL-Unterstützung befindet sich noch in aktiver Entwicklung (relativ neu wurde beispielsweise die Unterstützung für prepared statements) hinzugefügt, und laut der Liste issues, stehen dem SQL-Engine in Zukunft eine Reihe von Optimierungen bevor.

Async

Nun, sehen wir uns an, wie das Async-Extension uns helfen kann, die obigen Ergebnisse zu verbessern. Für das Schreiben asynchroner Programme bietet das Extension eine API auf Basis von Koroutinen (coroutines), die wir nutzen werden. Durch Experimente stellen wir fest, dass die optimale Anzahl an Koroutinen für unsere Umgebung 25 beträgt:

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Wir "verteilen" 10.000 Operationen auf 25 Koroutinen und schauen, was dabei herauskommt:

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Die Anzahl der Operationen pro Sekunde ist um mehr als das Dreifache gestiegen für tarantool-php/client!

Leider konnte der PECL-Connector nicht mit ext-async gestartet werden.

Und was ist mit SQL?

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Wie Sie sehen können, ist der Unterschied zwischen dem binären Protokoll und SQL im asynchronen Modus vernachlässigbar geworden.

Swoole

Erneut ermitteln wir die optimale Anzahl an Koroutinen, jetzt für Swoole:
Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Wir bleiben bei 25. Wir wiederholen den gleichen Trick wie mit dem Async-Extension – verteilen 10.000 Operationen auf 25 Koroutinen. Darüber hinaus fügen wir einen weiteren Test hinzu, bei dem wir die gesamte Arbeit auf 2 Prozesse aufteilen (das bedeutet, dass jeder Prozess 5.000 Operationen in 25 Koroutinen ausführt). Die Prozesse werden mittels SwooleProcess.

Ergebnisse:

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Swole zeigt einen etwas niedrigeren Wert im Vergleich zu Async, wenn es in einem Prozess gestartet wird, aber mit 2 Prozessen ändert sich das Bild grundlegend (die Zahl 2 wurde nicht zufällig gewählt, auf meinem Rechner haben genau 2 Prozesse das beste Ergebnis gezeigt).

Übrigens gibt es im Async-Extension auch eine API für die Arbeit mit Prozessen, jedoch habe ich dort keinen Unterschied bei der Ausführung der Benchmarks in einem oder mehreren Prozessen bemerkt (es ist möglich, dass ich irgendwo einen Fehler gemacht habe).

SQL vs. binäres Protokoll:

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Wie auch bei Async wird der Unterschied zwischen binären und SQL-Operationen im asynchronen Modus nivelliert.

Parallel

Da das Parallel-Extension nicht um Koroutinen, sondern um Threads geht, messen wir die optimale Anzahl an parallelen Threads:

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Sie beträgt 16 auf meinem Rechner. Lassen Sie uns die Benchmarks der Connectoren auf 16 parallelen Threads ausführen:

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Wie Sie sehen, ist das Ergebnis sogar besser als bei den asynchronen Erweiterungen (abgesehen von Swoole, das in 2 Prozessen gestartet wurde). Beachten Sie, dass für den PECL-Connector an der Stelle von Update- und Upsert-Operationen nichts steht. Das liegt daran, dass diese Operationen fehlerhaft ausgefallen sind – ich weiß nicht, ob es an ext-parallel, ext-tarantool oder an beiden liegt.

Jetzt vergleichen wir die Leistung von SQL:

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel
Haben Sie die Ähnlichkeit mit dem Diagramm für die Connectoren bemerkt, die synchron ausgeführt wurden?

Alles zusammen

Und schließlich fassen wir alle Ergebnisse in einem einzigen Diagramm zusammen, um das Gesamtbild der getesteten Erweiterungen zu sehen. Wir fügen dem Diagramm nur einen neuen Test hinzu, den wir noch nicht durchgeführt haben – wir starten die Async-Koroutinen parallel mit Hilfe von Parallel*. Die Idee, die oben genannten Erweiterungen zu integrieren, wurde bereits diskutiert von den Autoren, jedoch wurde kein Konsens erreicht, das müssen wir selbst erledigen.

* Es gelang nicht, die Swoole-Koroutinen mit Parallel zu starten; anscheinend sind diese Erweiterungen nicht kompatibel.

Also, die finalen Ergebnisse:

Wir beschleunigen PHP-Connectoren für Tarantool mit Async, Swoole und Parallel

Zum Abschluss

Meiner Meinung nach sind die Ergebnisse ziemlich ansprechend ausgefallen, und ich bin mir aus irgendeinem Grund sicher, dass dies noch nicht das Ende ist! Ob Sie das in Ihrem echten Projekt benötigen, liegt ganz bei Ihnen; ich sage nur, dass es für mich ein interessantes Experiment war, das es ermöglichte zu bewerten, wie viel man aus einem synchronen TCP-Connector mit minimalem Aufwand „herausquetschen“ kann. Wenn Sie Ideen zur Verbesserung der Benchmarks haben, freue ich mich über Ihren Pull-Request. Der gesamte Code mit Anweisungen zum Start und den Ergebnissen ist in einem separaten das Repository.

Quelle: habr.com

60GB SSD 8Gb DDR4