
Hallo habr
Wir schreiben und sprechen oft über die PHP-Leistung:
wie wir im Allgemeinen damit
umgehen ,
wie wir beim Wechsel zu PHP 7.0 1 Million US-Dollar
eingespart haben und auch verschiedene Materialien zu diesem Thema übersetzen. Dies liegt an der Tatsache, dass das Publikum unserer Produkte wächst und die Skalierung des PHP-Backends mit Eisen sehr teuer ist - wir haben 600 Server mit PHP-FPM. Daher ist es für uns von Vorteil, Zeit in die Optimierung zu investieren.
Vorher haben wir hauptsächlich über die üblichen und bereits etablierten Arbeitsweisen mit Produktivität gesprochen. Aber die PHP-Community ist in Alarmbereitschaft! JIT wird in PHP 8 erscheinen, Preload wird in PHP 7.4 erscheinen und Frameworks außerhalb des Kerns der PHP-Entwicklung werden entwickelt, die davon ausgehen, dass PHP als Daemon funktioniert. Es ist Zeit, mit etwas Neuem zu experimentieren und zu sehen, was uns dies geben kann.
Da die Veröffentlichung von PHP 8 noch weit entfernt ist und asynchrone Frameworks für unsere Aufgaben schlecht geeignet sind (warum - ich werde es weiter unten erläutern), konzentrieren wir uns heute auf das Preload, das in PHP 7.4 erscheinen wird, und das Framework zur Dämonisierung von PHP, RoadRunner.
Dies ist die Textversion meines Berichts mit
Badoo PHP Meetup # 3 . Video aller Reden, die wir
in diesem Beitrag gesammelt haben .
PHP-FPM, Apache mod_php und ähnliche Methoden zum Ausführen von PHP-Skripten und -Prozessanforderungen (die von der überwiegenden Mehrheit der Websites und Dienste ausgeführt werden; der Einfachheit halber werde ich sie als „klassisches“ PHP bezeichnen) arbeiten auf der Grundlage von
Shared-Nothing im weiteren Sinne des Wortes:
- Staat wird nicht zwischen PHP-Arbeitern durchsucht;
- Der Staat wird nicht zwischen verschiedenen Anfragen durchsucht.
Betrachten Sie dies anhand eines Beispiels für ein einfaches Skript:
Für jede Anforderung wird das Skript von der ersten bis zur letzten Zeile ausgeführt: Obwohl sich die Initialisierung höchstwahrscheinlich nicht von der Anforderung zur Anforderung unterscheidet und möglicherweise einmal ausgeführt werden kann (Ressourcen sparen), müssen Sie sie dennoch für jede Anforderung wiederholen. Aufgrund der Besonderheiten der Funktionsweise des „klassischen“ PHP können wir nicht einfach Variablen (z. B.
$app
) zwischen Anforderungen aufnehmen und speichern.
Wie würde es aussehen, wenn wir über den Rahmen von "klassischem" PHP hinausgehen würden? Zum Beispiel könnte unser Skript unabhängig von der Anforderung ausgeführt werden, initialisiert werden und eine Abfrageschleife enthalten, in der er auf die nächste warten, sie verarbeiten und die Schleife wiederholen würde, ohne die Umgebung zu bereinigen (im Folgenden werde ich diese Lösung „PHP als Daemon“ nennen ").
Wir konnten nicht nur die für jede Anforderung wiederholte Initialisierung entfernen, sondern auch die Liste der Städte einmal in der Variablen "
$cities
speichern und aus verschiedenen Anforderungen verwenden, ohne auf irgendwo anders als auf den Speicher zuzugreifen (dies ist der schnellste Weg, um Daten abzurufen).
Die Leistung einer solchen Lösung ist möglicherweise erheblich höher als die des "klassischen" PHP. Aber normalerweise ist die Steigerung der Produktivität nicht kostenlos - Sie müssen einen Preis dafür bezahlen. Mal sehen, was es in unserem Fall sein kann.
Lassen Sie uns dazu unser Skript etwas komplizieren und anstatt die Variable
$name
anzuzeigen, füllen wir das Array:
- $name = $cities[$req->getCookie('city_id')]; + $names[] = $cities[$req->getCookie('city_id')];
Bei „klassischem“ PHP treten keine Probleme auf - am Ende der Abfrage wird die Variable
$name
zerstört und jede nachfolgende Anforderung funktioniert wie erwartet. Wenn Sie PHP als Daemon starten, fügt jede Anforderung dieser Variablen eine weitere Stadt hinzu, was zu einem unkontrollierten Wachstum des Arrays führt, bis der Speicher auf dem Computer leer ist.
Im Allgemeinen kann nicht nur der Speicher enden - es können auch andere Fehler auftreten, die zum Tod des Prozesses führen. Bei solchen Problemen wird "klassisches" PHP automatisch behandelt. Wenn Sie PHP als Daemon starten, müssen Sie diesen Daemon irgendwie überwachen und neu starten, wenn er abstürzt.
Fehler dieser Art sind unangenehm, aber es gibt wirksame Lösungen für sie. Es ist viel schlimmer, wenn das Skript aufgrund eines Fehlers nicht herunterfällt, sondern die Werte einiger Variablen unvorhersehbar ändert (z. B. wird das Array
$cities
gelöscht). In diesem Fall funktionieren alle nachfolgenden Anforderungen mit falschen Daten.
Zusammenfassend ist es einfacher, Code für „klassisches“ PHP (PHP-FPM, Apache mod_php und dergleichen) zu schreiben - es befreit uns von einer Reihe von Problemen und Fehlern. Dafür zahlen wir aber mit Leistung.
Aus den obigen Beispielen geht hervor, dass PHP in einigen Teilen des Codes Ressourcen ausgibt, die nicht für die Verarbeitung jeder Anforderung des „klassischen“ Codes aufgewendet (oder einmal verschwendet) werden konnten. Dies sind die folgenden Bereiche:
- Dateiverbindung (einschließen, erfordern usw.);
- Initialisierung (Framework, Bibliotheken, DI-Container usw.);
- Daten vom externen Speicher anfordern (anstatt im Speicher zu speichern).
PHP gibt es schon seit vielen Jahren und es könnte dank dieses Arbeitsmodells sogar populär geworden sein. In dieser Zeit wurden viele Methoden mit unterschiedlichem Erfolg entwickelt, um das beschriebene Problem zu lösen. Ich habe einige davon in meinem vorherigen
Artikel erwähnt . Heute werden wir uns mit zwei ziemlich neuen Lösungen für die Community befassen: Preload und RoadRunner.
Vorladen
Von den drei oben aufgeführten Punkten
ist das
Preload darauf ausgelegt, den ersten Overhead beim Verbinden von Dateien zu bewältigen. Auf den ersten Blick mag dies seltsam und bedeutungslos erscheinen, da PHP bereits über OPcache verfügt, der nur für diesen Zweck erstellt wurde. Um die Essenz zu verstehen, lassen Sie uns mit Hilfe von
perf
, über das OPcache aktiviert ist, ein reales Profil mit einer Trefferquote von 100% erstellen.

Trotz OPcache sehen wir, dass
persistent_compile_file
5,84% der Ausführungszeit der Abfrage benötigt.
Um zu verstehen, warum dies geschieht, können wir uns die Quellen von
zend_accel_load_script ansehen . Aus ihnen ist ersichtlich, dass trotz des Vorhandenseins von OPcache bei jedem Aufruf zum Einschließen
include/require
Signaturen von Klassen und Funktionen aus dem gemeinsam genutzten Speicher in den Speicher des Arbeitsprozesses kopiert werden und verschiedene Hilfsarbeiten ausgeführt werden. Und diese Arbeit sollte für jede Anforderung ausgeführt werden, da am Ende der Speicher des Arbeitsprozesses gelöscht wird.

Hinzu kommt die große Anzahl von Include / Require-Anrufen, die wir normalerweise in einer einzigen Anfrage tätigen. Beispielsweise enthält Symfony 4 ungefähr 310 Dateien, bevor die erste nützliche Codezeile ausgeführt wird. Manchmal geschieht dies implizit: Um eine unten gezeigte Instanz der Klasse A zu erstellen, lädt PHP alle anderen Klassen (B, C, D, E, F, G) automatisch. Insbesondere in dieser Hinsicht fallen die Abhängigkeiten von Composer auf, die Funktionen deklarieren: Um sicherzustellen, dass diese Funktionen während der Ausführung von Benutzercode verfügbar sind, muss Composer sie unabhängig von der Verwendung immer verbinden, da PHP keine Autoload-Funktionen hat und dies auch nicht sein kann zum Zeitpunkt des Anrufs geladen.
class A extends \B implements \C { use \D; const SOME_CONST = \E::E1; private static $someVar = \F::F1; private $anotherVar = \G::G1; }
Wie das Vorspannen funktioniert
Preload hat eine einzige Haupteinstellung, opcache.preload, an die der Pfad zum PHP-Skript übergeben wird. Dieses Skript wird beim Starten von PHP-FPM / Apache / usw. einmal ausgeführt, und alle Signaturen von Klassen, Methoden und Funktionen, die in dieser Datei deklariert werden, stehen allen Skripten zur Verfügung, die Anforderungen ab der ersten Zeile ihrer Ausführung verarbeiten (wichtig) Hinweis: Dies gilt nicht für Variablen und globale Konstanten. Ihre Werte werden nach dem Ende der Vorladephase auf Null zurückgesetzt. Sie müssen keine Include- / Require-Aufrufe mehr vornehmen und Funktions- / Klassensignaturen aus dem gemeinsam genutzten Speicher in den Prozessspeicher kopieren: Sie werden alle als
unveränderlich deklariert, und aus diesem Grund können alle Prozesse auf denselben Speicherort verweisen, der sie enthält.
Normalerweise befinden sich die benötigten Klassen und Funktionen in verschiedenen Dateien, und es ist unpraktisch, sie in einem Preload-Skript zu kombinieren. Dies muss jedoch nicht durchgeführt werden: Da das Preload ein reguläres PHP-Skript ist, können wir einfach include / require oder opcache_compile_file () aus dem Preload-Skript für alle benötigten Dateien verwenden. Da alle diese Dateien einmal geladen werden, kann PHP außerdem zusätzliche Optimierungen vornehmen, die nicht durchgeführt werden konnten, während wir diese Dateien zum Zeitpunkt der Abfrage separat verbunden haben. PHP nimmt Optimierungen nur im Rahmen jeder einzelnen Datei vor, im Falle des Vorladens jedoch für den gesamten Code, der in der Vorladephase geladen wird.
Benchmarks Vorspannung
Um die Vorteile des Preloads in der Praxis zu demonstrieren, habe ich einen CPU-gebundenen Endpunkt Badoo verwendet. Unser Backend ist im Allgemeinen durch eine CPU-gebundene Last gekennzeichnet. Diese Tatsache ist die Antwort auf die Frage, warum wir asynchrone Frameworks nicht berücksichtigt haben: Sie bieten keinen Vorteil bei CPU-gebundener Auslastung und komplizieren gleichzeitig den Code noch mehr (er muss anders geschrieben werden) sowie für die Arbeit mit einem Netzwerk, einer Festplatte usw. Spezielle asynchrone Treiber sind erforderlich.
Um die Vorteile des Vorladens voll auszuschöpfen, habe ich für das Experiment alle Dateien heruntergeladen, die für das getestete Skript bei der Arbeit erforderlich sind, und es mit einem Anschein einer normalen Produktionslast mit
wrk2 geladen - einem fortgeschritteneren Analogon von Apache Benchmark, aber genauso einfach .
Um das Vorladen zu versuchen, müssen Sie zuerst auf PHP 7.4 aktualisieren (wir haben jetzt PHP 7.2). Ich habe die Leistung von PHP 7.2, PHP 7.4 ohne Vorspannung und PHP 7.4 mit Vorspannung gemessen. Das Ergebnis ist ein solches Bild:

Somit ergibt der Übergang von PHP 7.2 zu PHP 7.4 + 10% für die Leistung an unserem Endpunkt, und die Vorlast ergibt weitere 10% von oben.
Beim Vorladen hängen die Ergebnisse stark von der Anzahl der verbundenen Dateien und der Komplexität der ausführbaren Logik ab: Wenn viele Dateien verbunden sind und die Logik einfach ist, liefert das Vorladen mehr als wenn nur wenige Dateien vorhanden sind und die Logik komplex ist.
Die Nuancen der Vorspannung
Was die Produktivität erhöht, hat normalerweise einen Nachteil. Preload hat viele Nuancen, die ich unten geben werde. Alle müssen berücksichtigt werden, aber nur eine (erste) kann von grundlegender Bedeutung sein.
Ändern - neu starten
Da alle Preload-Dateien nur beim Start kompiliert, als unveränderlich markiert und in Zukunft nicht neu kompiliert werden, besteht die einzige Möglichkeit, Änderungen an diesen Dateien zu übernehmen, darin, PHP-FPM / Apache / usw. neu zu starten (neu zu laden oder neu zu starten).
Beim Neuladen versucht PHP, so genau wie möglich neu zu starten: Benutzeranforderungen werden nicht unterbrochen, aber während der Vorladephase warten alle neuen Anforderungen auf den Abschluss. Wenn sich beim Vorladen nicht viel Code befindet, kann dies keine Probleme verursachen. Wenn Sie jedoch versuchen, die gesamte Anwendung herunterzuladen, erhöht sich die Antwortzeit während eines Neustarts erheblich.
Auch ein Neustart (unabhängig davon, ob er neu geladen oder neu gestartet wird) hat eine wichtige Funktion - als Ergebnis dieser Aktion wird OPcache gelöscht. Das heißt, alle Anfragen danach funktionieren mit einem kalten Opcode-Cache, was die Antwortzeit noch weiter verlängern kann.
Undefinierte Zeichen
Damit eine Klasse vor dem Laden geladen werden kann, muss bis zu diesem Punkt alles definiert werden, von dem sie abhängt. Für die folgende Klasse bedeutet dies, dass alle anderen Klassen (B, C, D, E, F, G), die Variable
$someGlobalVar
und die Konstante SOME_CONST verfügbar sein müssen, bevor diese Klasse kompiliert wird. Da das Preload-Skript nur regulärer PHP-Code ist, können wir einen Autoloader definieren. In diesem Fall wird alles, was mit anderen Klassen verbunden ist, automatisch von ihr geladen. Dies funktioniert jedoch nicht mit Variablen und Konstanten: Wir müssen selbst sicherstellen, dass sie zum Zeitpunkt der Deklaration dieser Klasse definiert sind.
class A extends \B implements \C { use \D; const SOME_CONST = \E::E1; private static $someVar = \F::F1; private $anotherVar = \G::G1; private $varLink = $someGlobalVar; private $constLink = SOME_CONST; }
Glücklicherweise enthält das Preload genügend Tools, um zu verstehen, ob Sie etwas aus dem Weg räumen oder nicht. Erstens sind dies Warnmeldungen mit Informationen darüber, was nicht geladen werden konnte und warum:
PHP Warning: Can't preload class MyTestClass with unresolved initializer for constant RAND in /local/preload-internal.php on line 6 PHP Warning: Can't preload unlinked class MyTestClass: Unknown parent AnotherClass in /local/preload-internal.php on line 5
Zweitens fügt das Preload dem Ergebnis der Funktion opcache_get_status () einen separaten Abschnitt hinzu, der zeigt, was in der Preload-Phase erfolgreich geladen wurde:

Klassenfeld / konstante Optimierung
Wie ich oben geschrieben habe, löst Preload die Werte der Felder / Konstanten der Klasse auf und speichert sie. Auf diese Weise können Sie den Code optimieren: Während der Verarbeitung der Anforderung sind die Daten bereit und müssen nicht aus anderen Daten abgeleitet werden. Dies kann jedoch zu nicht offensichtlichen Ergebnissen führen, die das folgende Beispiel zeigt:
const.php: <?php define('MYTESTCONST', mt_rand(1, 1000));
preload.php: <?php include 'const.php'; class MyTestClass { const RAND = MYTESTCONST; }
script.php: <?php include 'const.php'; echo MYTESTCONST, ', ', MyTestClass::RAND;
Das Ergebnis ist eine kontraintuitive Situation: Es scheint, dass die Konstanten gleich sein sollten, da einer von ihnen der Wert des anderen zugewiesen wurde, aber in Wirklichkeit ist dies nicht so. Dies liegt an der Tatsache, dass globale Konstanten im Gegensatz zu Klassenkonstanten / -feldern nach Ende der Vorladephase zwangsweise gelöscht werden, während Klassenkonstanten / -felder aufgelöst und gespeichert werden. Dies führt dazu, dass wir während der Ausführung der Anfrage die globale Konstante erneut definieren müssen, wodurch sie einen anderen Wert erhalten kann.
SomeFunc () kann nicht neu deklariert werden
Bei Klassen ist die Situation einfach: Normalerweise verbinden wir sie nicht explizit, sondern verwenden einen Autoloader. Dies bedeutet, dass wenn eine Klasse in der Preload-Phase definiert ist, der Autoloader während der Anforderung einfach nicht ausgeführt wird und wir nicht versuchen werden, diese Klasse ein zweites Mal zu verbinden.
Bei Funktionen ist die Situation anders: Wir müssen sie explizit verbinden. Dies kann dazu führen, dass wir im Preload-Skript alle erforderlichen Dateien mit Funktionen verbinden und dies während der Anforderung erneut versuchen (ein typisches Beispiel ist der Composer-Bootloader: Er versucht immer, alle Dateien mit Funktionen zu verbinden). In diesem Fall erhalten wir eine Fehlermeldung: Die Funktion wurde bereits definiert und kann nicht neu definiert werden.
Dieses Problem kann auf verschiedene Arten gelöst werden. Im Fall von Composer können Sie beispielsweise alles in der Vorladephase verbinden und bei Anforderungen nichts mit Composer zu tun haben. Eine andere Lösung besteht nicht darin, Dateien direkt mit Funktionen zu verbinden, sondern dies über eine Proxy-Datei mit der Suche nach function_exists () zu tun, wie dies beispielsweise bei Guzzle HTTP der
Fall ist .

PHP 7.4 wurde (noch) nicht offiziell veröffentlicht
Diese Nuance wird nach einiger Zeit irrelevant, aber bis die PHP-Version 7.4 noch nicht offiziell veröffentlicht wurde und das PHP-Team ausdrücklich in den Versionshinweisen
schreibt : "Bitte verwenden Sie diese Version NICHT in der Produktion, es ist eine frühe Testversion." Während unserer Experimente mit Vorspannung sind wir auf mehrere Fehler gestoßen, haben sie selbst behoben und sogar etwas an den Upstream
gesendet . Um Überraschungen zu vermeiden, ist es besser, auf die offizielle Veröffentlichung zu warten.
Roadrunner
RoadRunner ist ein in Go geschriebener Daemon, der einerseits PHP-Worker erstellt und überwacht (bei Bedarf startet / endet / neu startet), andererseits Anforderungen akzeptiert und an diese Worker weiterleitet. In diesem Sinne unterscheidet sich seine Arbeit nicht von der Arbeit von PHP-FPM (wo es auch einen Master-Prozess gibt, der die Arbeiter überwacht). Aber es gibt immer noch Unterschiede. Der Schlüssel ist, dass RoadRunner den Status des Skripts nach Abschluss der Abfrage nicht zurücksetzt.
Wenn wir uns also an unsere Liste erinnern, welche Ressourcen im Fall von „klassischem“ PHP verbraucht werden, können Sie mit RoadRunner alle Punkte behandeln (das Vorladen erfolgt, wie wir uns erinnern, nur mit dem ersten):
- Dateiverbindung (einschließen, erfordern usw.);
- Initialisierung (Framework, Bibliotheken, DI-Container usw.);
- Daten vom externen Speicher anfordern (anstatt im Speicher zu speichern).
Das Hello World RoadRunner-Beispiel sieht ungefähr so aus:
$relay = new Spiral\Goridge\StreamRelay(STDIN, STDOUT); $psr7 = new Spiral\RoadRunner\PSR7Client(new Spiral\RoadRunner\Worker($relay)); while ($req = $psr7->acceptRequest()) { $resp = new \Zend\Diactoros\Response(); $resp->getBody()->write("hello world"); $psr7->respond($resp); }
Wir werden versuchen, unseren aktuellen Endpunkt, den wir mit Vorspannung getestet haben, ohne Änderungen auf RoadRunner auszuführen, ihn zu laden und die Leistung zu messen. Keine Änderungen - sonst ist der Benchmark nicht ganz ehrlich.
Versuchen wir, das Hello World-Beispiel dafür anzupassen.
Erstens möchten wir, wie ich oben geschrieben habe, nicht, dass der Arbeiter im Fehlerfall fällt. Dazu müssen wir alles in einen globalen Versuch einschließen. Zweitens, da unser Skript nichts über Zend Diactoros weiß, müssen wir für die Antwort seine Ergebnisse konvertieren. Dafür verwenden wir ob_-Funktionen. Drittens weiß unser Skript nichts über die Art der PSR-7-Anforderung. Die Lösung besteht darin, die Standard-PHP-Umgebung aus diesen Entitäten zu füllen. Und viertens erwartet unser Skript, dass die Anfrage stirbt und der gesamte Status gelöscht wird. Daher müssen wir diese Reinigung mit RoadRunner selbst durchführen.
So wird die ursprüngliche Hello World-Version ungefähr so:
while ($req = $psr7->acceptRequest()) { try { $uri = $req->getUri(); $_COOKIE = $req->getCookieParams(); $_POST = $req->getParsedBody(); $_SERVER = [ 'REQUEST_METHOD' => $req->getMethod(), 'HTTP_HOST' => $uri->getHost(), 'DOCUMENT_URI' => $uri->getPath(), 'SERVER_NAME' => $uri->getHost(), 'QUERY_STRING' => $uri->getQuery(),
Benchmarks RoadRunner
Nun, es ist Zeit, Benchmarks zu starten.

Die Ergebnisse entsprechen nicht den Erwartungen: Mit RoadRunner können Sie mehr Faktoren ausgleichen, die zu Leistungsverlusten führen als die Vorspannung. Die Ergebnisse sind jedoch schlechter. Lassen Sie uns herausfinden, warum dies wie immer geschieht, indem wir perf dafür ausführen.

In den Perf-Ergebnissen sehen wir phar_compile_file. Dies liegt daran, dass wir während der Ausführung des Skripts einige Dateien einschließen. Da OPcache nicht aktiviert ist (RoadRunner führt Skripte als CLI aus, wobei OPcache standardmäßig deaktiviert ist), werden diese Dateien bei jeder Anforderung erneut kompiliert.
Bearbeiten Sie die RoadRunner-Konfiguration - aktivieren Sie OPcache:


Diese Ergebnisse entsprechen bereits eher unseren Erwartungen: RoadRunner zeigte mehr Leistung als Vorspannung. Aber vielleicht können wir noch mehr bekommen!
Perf scheint nichts Ungewöhnlicheres zu sein - schauen wir uns den PHP-Code an. Der einfachste Weg, es zu profilieren, ist die Verwendung von
phpspy : Es ist keine Änderung des PHP-Codes erforderlich - Sie müssen ihn nur in der Konsole ausführen. Lassen Sie uns dies tun und ein Flammendiagramm erstellen:

Da wir vereinbart haben, die Logik unserer Anwendung für die Reinheit des Experiments nicht zu ändern, interessieren wir uns für den Stapelzweig, der mit der Arbeit von RoadRunner verbunden ist:

Der Hauptteil davon besteht darin, fread () aufzurufen. Damit kann kaum etwas getan werden. Wir sehen jedoch einige andere Zweige in
\ Spiral \ RoadRunner \ PSR7Client :: acceptRequest () , außer fread selbst. Sie können ihre Bedeutung anhand des Quellcodes verstehen:
public function acceptRequest() { $rawRequest = $this->httpClient->acceptRequest(); if ($rawRequest === null) { return null; } $_SERVER = $this->configureServer($rawRequest['ctx']); $request = $this->requestFactory->createServerRequest( $rawRequest['ctx']['method'], $rawRequest['ctx']['uri'], $_SERVER ); parse_str($rawRequest['ctx']['rawQuery'], $query); $request = $request ->withProtocolVersion(static::fetchProtocolVersion($rawRequest['ctx']['protocol'])) ->withCookieParams($rawRequest['ctx']['cookies']) ->withQueryParams($query) ->withUploadedFiles($this->wrapUploads($rawRequest['ctx']['uploads']));
Es wird deutlich, dass RoadRunner versucht, ein PSR-7-kompatibles Anforderungsobjekt mithilfe eines serialisierten Arrays zu erstellen. Wenn Ihr Framework direkt mit PSR-7-Abfrageobjekten arbeitet (z. B. Symfony
funktioniert nicht ), ist dies vollständig gerechtfertigt. In anderen Fällen wird der PSR-7 zu einem zusätzlichen Link, bevor die Anforderung in das konvertiert wird, mit dem Ihre Anwendung arbeiten kann. Entfernen wir diesen Zwischenlink und sehen uns die Ergebnisse noch einmal an:

Das Testskript war recht einfach, so dass ich einen signifikanten Teil der Leistung herausholen konnte - + 17% im Vergleich zu reinem PHP (ich erinnere mich, dass das Preload + 10% für dasselbe Skript ergibt).
Nuancen von RoadRunner
Im Allgemeinen ist die Verwendung von RoadRunner eine schwerwiegendere Änderung als nur die Einbeziehung der Vorspannung, sodass die Nuancen hier noch bedeutender sind.
-, RoadRunner, , PHP- , , , : , , .
-, RoadRunner , «» — . / RoadRunner ; , , , , - .
-, endpoint', , , RoadRunner. .
Fazit
, «» PHP, , preload RoadRunner.
PHP «» (PHP-FPM, Apache mod_php ) . - , . , , preload JIT.
, , , RoadRunner, .
, (: ):
- PHP 7.2 — 845 RPS;
- PHP 7.4 — 931 RPS;
- RoadRunner — 987 RPS;
- PHP 7.4 + preload — 1030 RPS;
- RoadRunner — 1089 RPS.
Badoo PHP 7.4 , ( ).
RoadRunner , , , , .
Vielen Dank für Ihre Aufmerksamkeit!