
JIT-Compiler in PHP 8
PHP ist eine interpretierte Programmiersprache, die lange Zeit mit der Verbesserung ihrer Leistung zu kämpfen hatte, was im Vergleich zu anderen konkurrierenden Programmiersprachen, die in der Webentwicklung verwendet werden, besonders düster war. Unter den Neuerungen in PHP 8 verdient der JIT-Compiler besondere Aufmerksamkeit, da er offiziell in die neueste Version aufgenommen wurde. Wird dieser Schritt ein echter Durchbruch sein oder nur ein blasser Lichtblick, eine interessante Ergänzung der Sprache? Sehen wir uns an, welche echten Vorteile er bringen kann und ob er eine echte Verbesserung der Leistung Ihrer Anwendung bringt.
Einige Leser erinnern sich vielleicht nicht daran, aber seit PHP 5.5 wurde OPcache, das von der Zend VM betrieben wird, ein integraler Bestandteil der Sprache. Dies war ein Durchbruch in Bezug auf die Verbesserung der Anwendungsleistung und damit im Kontext der Dienstleistungserbringung von PHP-Webentwicklung. Die spätere PHP 7 Veröffentlichung brachte noch bessere Leistung. Bereits in diesem Stadium gab es Vorschläge, einen JIT-Compiler in die Sprache zu implementieren und hinzuzufügen; die Community unternahm jedoch alle Anstrengungen, um die Effizienz mit den verfügbaren Mechanismen zu maximieren.
Was ist JIT?
Einfach gesagt, JIT (Just-in-Time-Kompilierung) ist eine Technik, die es ermöglicht, ein Programm direkt vor seiner Ausführung in Maschinencode zu kompilieren. In der Praxis führt dieser Ansatz zu einer deutlich schnelleren Ausführung des Codes im Vergleich zu einem herkömmlichen Interpreter. Manchmal wird JIT als goldener Mittelweg zwischen Flexibilität bei der Entwicklung des Anwendungscodes und seiner Laufzeitleistung bezeichnet.
Funktionsprinzip
Wie ich bereits erwähnt habe, basierte die bessere PHP-Leistung bis heute auf OPCache mit dem sogenannten OPCode, einem vorkompilierten Codeausschnitt in Form von Befehlen, die dem Prozessor zur Ausführung gegeben werden und nach dem ersten Ausführen des Skripts erstellt werden. Der zwischengespeicherte Code kann fast sofort von einer virtuellen Maschine ausgeführt werden; er ist jedoch immer noch kein nativer Maschinencode.
JIT bietet tatsächlichen Maschinencode, der so implementiert wurde, dass er zusammen mit OPCache funktioniert. Wenn das Skript zum ersten Mal gestartet wird, wird es, wenn es bereits in OPCache zwischengespeichert wurde, sofort zurückgegeben und kompiliert (wenn es nicht bereits war). Falls das Skript jedoch noch nicht zwischengespeichert wurde, durchläuft es zunächst den gesamten OPCode-Erzeugungsprozess wie zuvor. Der gesamte Prozess wird im folgenden Diagramm perfekt veranschaulicht.
JIT-Konfiguration
Wenn Sie denken, JIT sei nur ein Teil der Sprache / des Interpreters und funktioniere automatisch, dann irren Sie sich. Leider erfordert es eine zusätzliche Konfiguration, die auf den ersten Blick nicht besonders benutzerfreundlich und offensichtlich erscheint. Der gesamte Vorgang wird in der php.ini-Konfigurationsdatei ausgeführt, die PHP-Programmierern gut bekannt ist.
Das erste Wichtige ist, dass JIT nur funktioniert, wenn OPCache aktiviert ist. Jede Standard-PHP-Installation hat diesen Wert (opcache.enable) sofort auf 1 gesetzt. Um JIT freizuschalten, müssen wir zwei Parameter setzen:
- opcache.jit_buffer_size
- opcache.jit
Der erste (opcache.jit_buffer_size) ist dafür verantwortlich, die Menge an Speicher zu definieren, die wir für die Codekompilierung zuweisen möchten. Das Setzen eines Beispielswertes ist daher ziemlich einfach:
opcache.jit_buffer_size=256M
Was den zweiten Parameter (opcache.jit) betrifft, teilt er Ihnen im Grunde mit, wie JIT funktionieren soll. Lassen Sie mich zur Abwechslung mit einem Beispiel beginnen:
opcache.jit=1255
Ja, ich war auch überrascht von dem mysteriösen numerischen Wert. Zuerst dachte ich, es sei irgendeine Art von Bitmaske oder dergleichen. Doch als ich die RFC analysierte, in der wir mehr Details über die einzelnen Optionen finden, stellte sich heraus, dass jede dieser Ziffern separat ein spezifischer Konfigurationswert ist. Ich möchte daher drei Konfigurationswerte vorstellen, die möglicherweise am häufigsten verwendet werden. Sie sind eine Abkürzung, um Entwicklern beim Setzen des gewünschten Modus zu helfen.
- opcache.jit = 1205 - der gesamte Code wird JIT-kompiliert
- opcache.jit = 1235 - nur ausgewählte Codeabschnitte (basierend auf ihrer relativen Nutzung) werden der JIT-Kompilierung zugeführt
- opcache.jit = 1255 - Anwendungs-Code wird zur Kompilierung durch JIT verfolgt und bestimmte Teile des Codes werden an den Compiler übergeben
Natürlich war ich nicht der Einzige, der auf die fragwürdige Zugänglichkeit einer solchen Konfiguration hingewiesen hat. Daher wurden Korrekturen zum erwähnten RFC hinzugefügt, in Form von zwei Aliassen tracing und function, die anstelle von numerischen Werten verwendet werden können, z.B.
opcache.jit=tracing
Der Unterschied zwischen diesen Modi ist, dass JIT beim Verwenden des Wertes function den Code nur im Rahmen einer Funktion optimiert. Bei der Verwendung von tracing hingegen wird der gesamte Stack-Trace betrachtet und optimierbarer Code gesucht. Von diesen beiden Optionen wird JIT tracing am meisten empfohlen, da es in Benchmarks die vielversprechendsten Ergebnisse liefert und die Anwendungsleistung erheblich steigert.
Auswirkungen auf die Leistung von Webanwendungen
Jeder gute Entwickler sollte wissen, dass der wichtigste Faktor, der die Leistung einer Anwendung beeinflusst, die Qualität ihres Codes ist. Ein weiterer ebenso wichtiger Faktor ist die Wahl der Technologien, die ihren gesamten Stack bilden und deren Entwicklung. Im Falle von PHP war OPCache eine Revolution; jedoch scheint JIT – trotz des großen Medienrummels – nicht derselbe Meilenstein zu sein, insbesondere für Webanwendungen. Warum?
JIT wurde mit dem Ziel eingeführt, Codeabschnitte zu kompilieren, die nicht großen Schwankungen unterliegen. Es erkennt Codeausschnitte, die mehrmals ausgeführt werden, und kompiliert sie entsprechend. Wie Sie wahrscheinlich bereits vermuten, hängt die Ausführung von Code beim Bearbeiten einer einzelnen Anfrage/eines einzelnen Aufrufs einer Webanwendung von zu vielen Variablen ab, und in Wirklichkeit stellt sich heraus, dass diese identischen Codeausschnitte selten sind. Darüber hinaus kann es in einigen Fällen so wenige von ihnen geben, dass JIT anstelle einer wirklichen Beschleunigung der Anwendung tatsächlich eine Verlangsamung bewirkt, da der Code zusätzlich kompiliert werden muss.
In einem der öffentlich zugänglichen Tests des Projekts, das auf dem Laravel-Framework basiert und im medium.com-Artikel beschrieben wird, erfahren wir, dass es im PHP 8 mit JIT nur einen leichten Leistungsschub gibt:
PHP 7.3: 131.37 req/s
PHP 8.0 + JIT: 133.57 req/s
Man kann klar erkennen, dass die zusätzliche Leistung in Webanwendungen kaum spürbar sein wird.
Diese These wird in dem öffentlichen Benchmark bestätigt, das von der PHP-Gruppe im Rahmen der PHP 8-Veröffentlichung präsentiert wurde.
Wie Sie sehen können, erzielen Anwendungen, die für Webanwendungen wie WordPress, MediaWiki oder Symfony-Demo entwickelt wurden, ähnliche oder leicht höhere oder sogar niedrigere Ergebnisse (bei Verwendung von Function JIT) als beim Ausführen in PHP 8 ohne Verwendung von JIT. In anderen Fällen ist die Situation jedoch ziemlich anders. Bei synthetischen Benchmarks oder bei Aufgaben wie der Generierung von Fraktalen kann die Effizienz bis zu dreimal höher sein.
In anderen Fällen, wie zum Beispiel bei lang laufenden Anwendungen, ergibt sich ein Gewinn in der Leistungsfähigkeit von 1,5 - 2 mal. Wie Sie sehen, handelt es sich tatsächlich um einen Durchbruch, der neue Möglichkeiten bei der Nutzung der Sprache außerhalb typischer Webanwendungen bietet.
Wofür wird es dann verwendet?
Die Einführung eines JIT-Compilers in PHP ist tatsächlich ein Schritt zur Erschließung neuer Möglichkeiten der Sprache. JIT verbessert die Ausführungsleistung von Code signifikant bei CPU-intensiven Anwendungen. Dies dreht sich darum, PHP für völlig neue Zwecke zu nutzen, wie maschinelles Lernen, komplexe mathematische Berechnungen oder 2D/3D-Bildverarbeitung/-modellierung.
Ein Proof-of-Concept-Video, das von einem der führenden PHP-Entwickler (Zeev Suraski) erstellt wurde, kursiert schon seit einiger Weile im Internet und zeigt Leistungsverbesserungen dank JIT - in seinem Fall ging es um die Echtzeit-Generierung von Fraktalen.
Laut verschiedenen im Netz verfügbaren Benchmarks (zum Beispiel https://www.phoronix.com/scan.php?page=article&item=php8-jit-june&num=2) bietet JIT je nach Anwendung einen tatsächlichen, signifikanten Leistungsschub, der von einstelligen Verbesserungen bis hin zu fast hundert Prozent reicht.
Vor- und Nachteile
Die Implementierung des JIT-Compilers ist ein Schritt zur Erschließung neuer Möglichkeiten der Sprache und macht beispielsweise PHP perfekt für Start-ups. Die Möglichkeit, PHP-Code direkt zu kompilieren, wird die Abhängigkeit von C verringern, was die Entwicklung tatsächlich erleichtert und ohne besondere Leistungseinschränkungen ermöglicht. Unbestreitbar verbessert JIT die Anwendungsleistung in unterschiedlichem Maße.
Auf der anderen Seite kann JIT in seiner aktuellen Form die Leistung von Webanwendungen nicht signifikant verbessern und kann in bestimmten Szenarien sogar zu schlechteren Leistungen als zuvor führen. Seine Funktionsweise kann auch den Entwicklungsprozess beeinträchtigen, indem das Debuggen von Code erschwert wird, da es einige bekannte Probleme mit dem xDebug-Tool gibt. JIT erfordert auch ein zusätzliches Konfigurationswissen von den Entwicklern.
Fazit
Bei Droptica, wo wir Drupal-Dienste, Drupal-Unterstützung erbringen und unser Team viele PHP-Programmierer umfasst, wissen wir, dass obwohl JIT in der Welt nicht völlig neu ist (Facebook und ihr HHVM waren die Vorreiter), es mit der Freigabe von PHP 8 bald ein nativer Bestandteil der Sprache wird. JIT wird die Codeausführungsleistung verbessern und die Nutzungsmöglichkeiten der Sprache erweitern. Ich denke, dass wir in den kommenden Jahren seine Entwicklung im Auge behalten sollten, die noch bessere Ergebnisse bringen kann, insbesondere im Kontext von Webanwendungen.