PHP schneller & effizienter machen: So funktionieren OPcache, Variablen-Cache, JIT & co.

PHP schneller & effizienter machen: So funktionieren OPcache, Variablen-Cache, JIT & co.

Die Leistung von PHP-Anwendungen lässt sich deutlich verbessern. Das reduziert Ladezeiten & Serverlast. Während der OPcache jedes PHP-Projekt ohne Anpassungen beschleunigt, wird der Zwischenspeicher für Variablen in den Code integriert. Dieser Artikel stellt beide Varianten sowie den neuen JIT-Compiler vor und fasst ihre Umbrüche aus der historische Entwicklung zusammen. Er soll als Einstieg dienen, um Verständnis für die Funktionsweise von PHP aufzubauen. Sowie über die daraus entstehenden Probleme in der Leistungsfähigkeit. So lassen sich die gefundenen Lösungen verstehen. Im zweiten Teil werden wir sämtliche Optimierungen in der Praxis am Demo-Beispiel eines Apache-Webservers mit PHP-Modul anwenden.

Die Sprache des WWW

Seit Jahren wird PHP für tot erklärt, weil angeblich moderne Sprachen wie JavaScript (im gleichen Jahr entstanden) sie ablösen. Ebenso lange ist das nicht eingetroffen, im Gegenteil. Zum Stand März 2026 wird PHP laut W3Techs-Erhebung auf satten 72% der Webseiten eingesetzt – und liegt damit bis heute mit großem Abstand auf Platz 1.1 Alle danach erreichen nur einstellige Marktanteile. Selbst das vielbeschworene JS erzielt lediglich eine Verbreitung von 5,7%.

Nicht nur deswegen lohnt sich eine ausführlichere Auseinandersetzung mit PHP: Die Skriptsprache wies zwar fürchterliche Eigenheiten auf. Doch das hat sich seit Jahren kontinuierlich verbessert. Ab PHP 7 ist ein deutlicher Schub erkennbar. Die Verbesserungen der Sprache selbst würden den Rahmen sprengen und sind einen eigenen Beitrag wert. Hier soll der Fokus auf der Leistung liegen. Ein Kritikpunkt an PHP war die mangelnde Effizient. Er hatte seine Berechtigung – doch mittlerweile wurden sogar zahlreiche zuvor extern existierende Werkzeuge in den Kern der Sprache integriert, die vieles deutlich besser machen.

Bevor du erfährst, was sich konkret verbessert hat, erkläre ich die Gründe sowie Funktionsweise. Im späteren Verlauf wird auf die Konfiguration eingegangen. Schlussendlich legen wir uns eine simple Demo-Umgebung an, welche von Zwischenspeichern & weiteren Optimierungen profitieren.

So funktioniert PHP

Beide Arten funktionieren komplett unterschiedlich. Um das nachvollziehen zu können, ist ein grundlegendes Verständnis davon notwendig, wie ein Webserver PHP-Anfragen abarbeitet. Das funktioniert im Wesentlichen in vier Schritten. Gehen wir von einem bekannten Apache2 mit mod_php aus. In klassischen Webservern sind Skriptsprachen wie PHP nicht integriert, sodass diese über ein Modul an den PHP-Interpreter weitergeleitet werden.

  1. Der Browser sendet durch das Aufrufen einer Seite eine HTTP-Anfrage (GET /demo.php)
  2. Der Webserver startet den PHP-Interpreter & führt das Skript aus
  3. PHP erzeugt HTML als Ausgabe
  4. Diese Ausgabe wird vom Webserver als Antwort (HTTP Body) an den Browser gesendet

Optimierungspotenzial besteht in Schritt 2: PHP ist eine Interpreter-Sprache. Der Code wird bei jedem Aufruf neu vom Interpreter ausgeführt. Damit unterscheidet es sich von kompilierten Sprachen wie etwa C++ oder C: Sie werden vom Compiler einmalig in Maschinencode übersetzt. Es entsteht eine ausführbare Datei, die mit hoher Leistung nutzbar ist. Interpreter-Sprachen sind flexibler, doch auch langsamer & benötigen mehr Leistung – schließlich muss der Interpreter bei jeder HTTP-Anfrage den PHP-Code von der Festplatte lesen und ausführen.

OPcache macht den PHP-Interpreter effizienter

Für Webseiten oder Webanwendungen ist das wenig sinnvoll. Schließlich ändern sich PHP-Dateien nur selten – folglich führt der Interpreter dutzendfach den selben Code aus. Um das zu reduzieren, erzeugt OPcache mit Bytecode eine Zwischenschicht von PHP & dem Maschinencode. Er kann von einer VM (ab PHP 5.5 Zend) gelesen werden, wodurch der Rechenaufwand deutlich sinkt. Sie ist eine einheitliche Laufzeitumgebung und abstrahiert verschiedene Betriebssysteme sowie CPU-Architekturen. Da der Bytecode im Arbeitsspeicher liegt, entfallen zudem I/O Zugriffe. Schließlich teilt sich Code oft auf zig PHP-Dateien auf, die zuvor vom Laufwerk geladen werden müssen – ebenfalls bei jeder HTTP-Anfrage.

Ein OPcache ließt PHP-Skripte einmalig aus dem Dateisystem & speichert sie als Bytecode in den RAM. Insbesondere bei komplexeren Anwendungen, die aus vielen Codezeilen sowie Dateien bestehen, kann es die Geschwindigkeit deutlich verbessern. Parallel sinkt die Serverlast. Wie stark dies ausfällt, hängt vom konkreten Szenario ab. Da es sich hierbei um die interne Verarbeitung handelt, ist keine OPcache-Unterstützung der Anwendung erforderlich.

Wozu ein Variablen-Cache (bzw. User-Cache)?

Das HTTP-Protokoll ist zustandslos – bedeutet konkret: Jede Anfrage ist in sich geschlossen. Es werden keine Informationen aus Anfrage A in Anfrage B zur Verfügung gestellt. Die einzige Ausnahme sind Cookies, welche vom Browser in zukünftigen Anfragen mitgesendet werden. Auch PHP ist standardmäßig Zustandslos: Sämtliche Variablen und sonstige Objekte entsorgt der Garbage Collector, nachdem die Anfrage abgeschlossen wurde.

Wie man sich denken kann, ist das nicht ideal. Schließlich sind viele Elemente einer Seite für alle oder einen Großteil der Besucher identisch. Ein konkretes Beispiel wären die Widget-Boxen rechts für die neuesten Inhalte. Es ist unnötig, diese tausende male am Tag für jede Anfrage mit den gleichen Inhalten neu zu laden. Besonders kritisch wird es für die Performance, wenn diese eine aufwändige SQL-Abfrage erfordern. Oder sie ein API-Aufruf von einer externen Seite lädt. Neben der Geschwindigkeit kommen dort außerdem Blockaden hinzu, falls die Zahl der Anfragen überhand nimmt.

Die Lösung liegt auf der Hand: Ein Speicher, der Variablen über verschiedene Anfragen hinweg im RAM ablegt. Das ist der Variablen-Cache bzw. User-Cache. Über bestimmte PHP-Funktionen lassen sich Variablen in Anfrage A setzen, um sie in B & C abzurufen.

Prozessmanager wie PHP-FPM beschleunigen an anderer Stelle

An dieser Stelle sei auf ein weiteres gelöstes Problem hingewiesen: Am einfachsten lässt sich PHP mit Apache2 und dem PHP-Modul (mod_php) nutzen. Man muss lediglich das Modul installieren & aktivieren. Intern funktioniert es sehr simpel, in dem für jede HTTP-Verbindung ein PHP-Prozess gestartet wird (mpm_prefork Modus). Es werden also ständig neue, kurzlebige Prozesse gestartet. Je mehr Verbindungen, um so höher sind RAM-Verbrauch & CPU-Nutzung durch diese vielen Prozesse – das ist der erste Nachteil.

Durch die tiefe Integration in Apache hängt alles miteinander zusammen & verursacht unnötige Mehrlast: Jeder Apache-Prozess enthält den PHP-Interpreter sowie sämtliche Apache-Module. Eine statische Anfrage beinhaltet Bestandteile von PHP, während PHP-Anfragen umgekehrt (irrelevante) Module des Webservers mit sich schleppen. Das ist ineffizient, aber nicht zwingend ein Problem. Für weniger frequentiere Seiten kann es ausreichen.

Besser geht es durch die Trennung von Webserver & PHP: Prozessmanager wie PHP-FPM starten einen Pool aus PHP-Prozessen, die sich ausschließlich darum kümmern. Der Webserver leitet nur PHP-Anfragen dort hin weiter. Apache bietet npm_worker_mdoule, welches mehrere Threads pro Prozess startet. Allerdings ist PHP nicht threadsicher. PHP-FPM skaliert besser. Dies wird nur am Rande erwähnt, weil nichts mit dem Caching zu tun hat. Sondern mit dem grundsätzlichen Stack, davon sind die folgenden Optimierungen unabhängig: Sie können sowohl mit mod_php, als auch PHP-FPM verwendet werden.

Ein Schwenk in die Geschichte der PHP-Beschleuniger: Warum es ist, wie es ist

Bislang habe ich nur die allgemeine Funktionsweise beschrieben. Da PHP dieses Thema lange vernachlässigte, schuf die Gemeinschaft verschiedene Quelloffene Lösungen. Einer der ältesten ist APC: Der Alternative PHP Cache entstand in den frühen 2000er Jahren, bis er 2012 eingestellt wurde. XCache schuf 2006 ein Lighttpd-Entwickler – ihn habe ich damals erstmals auf U-Labs eingesetzt. Beide bieten sowohl OPCache, als auch das Zwischenspeichern von Variablen. Dagegen konnte eAccelerator nur den PHP-Code im interpretierten Zustand speichern.

Viele Projekte sahen sich als obsolet, als PHP mit Version 5.5 begann, die Zend OPcache Erweiterung auszuliefern.2 Ursprünglich war APU im Gespräch, doch das PHP-Team entschied sich für Zend. Bewusst soll sie nur Bytecode zwischenspeichern, keine Objekte aus dem Nutzercode. Modularität, Stabilität & geringe Komplexität wurden priorisiert. Dadurch fehlte weiterhin ein Objekt-Cache.

Diese Lücke wollte APC füllen und erlangte als APCu die Wiedergeburt. APC selbst musste eingestellt werden: Da es OPcache & Variablen-Cache enthält, war es nicht mehr mit PHP ab 5.5 kompatibel. Zumindest wenn Zend OPcache aktiviert wurde, kam es zum Konflikt. Zur Lösung entfernte APCu den OPcache. Somit rüstet man den fehlenden Zwischenspeicher für Variablen mittels APCu nach.

Weiterentwicklung des Zend OPcache

PHP 5.5 erschien initial im Jahr 2013. In den ~13 Jahren wurde der OPcache über verschiedene PHP-Versionen hinweg verbessert: In Version 7.0 wurde die Zend Engine in Version 3 komplett überarbeitet. Man kann den Cache zusätzlich im Dateisystem ablegen. Das beschleunigt die Neuverwendung nach dem Neustart, wodurch dieser bislang komplett neu aufgebaut werden musste. Oder falls der RAM-Cache ausgelastet ist. Generell brachte PHP 7.0 einige Performance-Verbesserungen gegenüber 5.x, man kann per Faustformel von etwa 50% ausgehen. Technisch wurde außerdem das Konzept des AST (Abstract Syntax Tree) eingeführt, welches Parser & Compiler aufteilt.

Den ersten Start verbessert PHP 7.4 mit Preloading. In der php.ini Direktive opcache.preload kann der Pfad zu einer PHP-Datei angegeben werden, welche direkt beim Start geladen & in den OPcache abgelegt werden soll. Zuvor bekam der erste Nutzer ein tendenziell schlechteres Erlebnis, vor allem beim Einsatz umfangreicher Frameworks.

Der JIT erweitert den OPcache

Ab PHP 8.0 kann JIT (Just-In-Time Compilation) zusammen mit OPcache sogar Maschinencode erzeugen.3 Bestimmte Codebereiche werden zur Laufzeit in Maschinencode statt Bytecode übersetzt. Es existieren zwei Arten: „Function JIT“ kompiliert Funktionen beim ersten Aufruf. Er ist stabiler, dafür fällt der Performance-Gewinn geringer aus. „Tracing JIT“ dagegen analysiert, welche Codepfade zur Laufzeit häufig ausgeführt werden & kompiliert nur diese – das kann effizienter sein, in synthetischen Benchmarks 1,5x bis 2x besser. Die OPcache Erweiterung ist seit dem nicht mehr optional, d.H. PHP lässt sich nur noch zusammen mit ihr kompilieren.

Dieser Weg wurde in PHP 8.1 beibehalten: Verbesserungen am JIT machen die Technik stabiler. Auch der OPcache profitiert von Optimierungen.4 Wie stark, variiert je nach Anwendung. Bei einer Symfony-Demo ist 8.1 um 23% schneller, WordPress beschleunigt sich um 3,5%. Außerdem ist der JIT nun auch für ARM64 und damit langfristig auf Einplatinencomputern wie dem Raspberry Pi verfügbar.

APCu oder Hype-Anwendungen wie Redis/Valkey/Memcached?

Das Konzept von Schlüssel-Wert-Speichern im Arbeitsspeicher ist keineswegs neu, sondern ebenfalls gewachsen. Viele werden Redis als bekanntesten Vertreter kennen, gegründet bereits 2009. Valkey ist eine 2024 von der Open Source Initiative geschaffene Abspaltung als Reaktion auf Redis Wechsel von BSD zu einem dualen Lizenzmodell. Memcached ist noch älter und erschien erstmals 2003.

Die grundsätzliche Funktion aller drei ist identisch, es gibt jedoch einen entscheidenden Unterschied: Redis, Valkey & Memcached sind verteilte Caches. Man verbindet sich per TCP, wie zu einem Datenbankserver. So wird Skalierung ermöglicht, weil sich der Schlüssel-Wert-Cache auf einem eigenen Server befinden kann – oder gar einem ganzen Cluster. Für sehr große Seiten lohnt sich das – trotz der höheren Latenz, die durch TCP-Verbindungen entsteht. Kleine bis mittelgroße Webanwendungen profitieren mehr von APCu. Dort liegt alles auf dem gleichen Server, mehr Leistung/Speicher ist ausschließlich durch Aufrüsten möglich.5

Oft wird Skalierung überschätzt. Man benötigt sehr viele Besucher (oder sehr ineffizienten Code), um einen halbwegs solide ausgestatteten Server an seine Grenzen zu bringen. Wer dennoch diese Ambitionen hat, baut sich eine Caching-Klasse zur Abstrahierung. Das hat mir bereits anderweitig geholfen: Beim Wechsel des mittlerweile eingestellten Xcache zu APCu.

Diese versteckten Bremsen gehören nicht zu PHP

Der klassische Ansatz ohne jegliche Zwischenspeicher war keine Sternstunde hoher Performance. Das ist offensichtlich – schließlich musste jede Anfrage zig PHP-Dateien laden und vom Interpreter ausführen lassen. Während dies seit langem gelöst ist, freut sich ein anderer Bremsklotz großer Beliebtheit: Frameworks.

Häufig werden sie nur zu einem Bruchteil ausgereizt und vergrößern die Angriffsfläche sowie den Wartungsaufwand. Caching kann ersteres nur abschwächen, gegen den Rest ist es machtlos. Ich verzichte daher in aller Regel auf riesige Frameworks wie Symfony & co. Für sinnvolle Konstrukte wie z.B. MVC habe ich eigene minimalistische Lösungen entwickelt. Sie sind bereits ohne Caching pfeilschnell und gehen dabei sparsam mit Ressourcen um.

Damit möchte ich Frameworks nicht pauschal für jeden Anwendungsfall verteufeln. Sie können nützlich sein – doch nur, wenn sie im Verhältnis zu den Anforderungen stehen. Hier scheitert es oft, etwa weil man sich mit Framework X auskennt. Daher kommt es zum Einsatz, obwohl eine schlankere Lösung ausreichen würde. Ebenfalls verbreitet ist Overengineering: Der Entwickler ist sich im klaren darüber, ein viel zu umfangreiches Framework einzusetzen. Doch er rechnet damit, dass vielleicht später noch mehr dazu kommt, womit es sich rechnet. Weitsicht ist vernünftig, doch sie muss Grenzen kennen. Oft kommt es nicht so weit. Dafür hat man bis dahin eine ineffiziente Anwendung mit hohem Wartungsaufwand. Schließlich erfordert das Framework regelmäßige Pflege & Anpassungen am Code.

Fazit

Es hat relativ lange gedauert, bis das PHP-Team mit Version 5.5 die Sinnhaftigkeit eines OPcache im Kern erkannt hat. Dafür wurde es im Laufe der Versionen mit neuer Engine (PHP 7.0), dem Aufwärmen (PHP 7.4) und ab PHP 8.0 dem JIT stetig verbessert. Ein Zwischenspeicher für Variablen fehlt zwar. Doch diese Lücke füllt APCu problemlos. Dies ist ein Gebiet, auf dem die Skriptsprache in den letzten Jahren deutlich aufgewertet wurde. Der Performance-Abstand zu dauerhaft laufenden Anwendungsservern (Gogs, NodeJS, .NET usw) schwindet weiter. Heute ist es leichter denn je, performante PHP-Webanwendungen zu entwickeln.

Nicht übersehen werden sollte: Der größte Killer sind umfangreiche Frameworks. Man kann sie mit OPcache & co. zwar weniger schlimm machen – doch niemals so performant, wie eine passende Lösung. Ganz zu schweigen vom höheren Aufwand und der gesteigerten Angriffsfläche. Statt zu versuchen, unnötig umfangreiche Frameworks auf dieser Ebene weniger langsam zu machen, sollte man daher lieber von Anfang an prüfen & hinterfragen: Was brauche ich wirklich? Ist ein Framework dafür tatsächlich notwendig, wenn ja welches?

Quellen

  1. https://w3techs.com/technologies/overview/programming_language ↩︎
  2. https://www.php.net/releases/5_5_0.php ↩︎
  3. https://www.php.net/releases/8.0/en.php ↩︎
  4. https://www.php.net/releases/8.1/en.php ↩︎
  5. https://reintech.io/blog/guide-php-apcu-library-caching-performance-optimization ↩︎

Leave a Reply