Ein ehrlicher Benchmark-Ansatz statt gefühlter Wahrheiten über PHP-String-Operationen
Kaum ein PHP-Thema wird so hartnäckig mit Halbwissen diskutiert wie die Frage, ob Concatenation, Interpolation, Heredoc oder sprintf am schnellsten ist. Die ehrliche Antwort lautet: Bei den allermeisten realen Anwendungen ist der Unterschied irrelevant, und wer trotzdem fundiert mitreden will, braucht einen sauberen Benchmark-Aufbau statt eines schnellen microtime-Vergleichs in einer Schleife. Dieser Artikel zeigt, wie man wirklich misst, was OPcache dabei verändert, und wann Lesbarkeit die Performance-Frage komplett dominieren sollte.
Inhaltsverzeichnis
- 1. Vier Wege, einen String zusammenzusetzen
- 2. Wie PHP Interpolation intern parst
- 3. Benchmark-Ansatz: Wie man String-Operationen valide misst
- 4. Tatsächliche Zahlen und ihre Interpretation
- 5. Heredoc und Nowdoc: Eigenheiten und wann sie Lesbarkeit gewinnen
- 6. sprintf und vsprintf: Formatierung typsicher trennen
- 7. OPcache-Einfluss auf String-Operationen und interned Strings
- 8. Praxisempfehlung je nach Kontext
- 9. Häufige Fehleinschätzungen und die Microoptimization-Falle
- 10. Zusammenfassung
- 11. FAQ
1. Vier Wege, einen String zusammenzusetzen
PHP bietet für denselben Zweck, dynamische Werte in einen Text einzubetten, mindestens vier unterschiedliche Syntaxformen, die sich in Lesbarkeit, Sicherheit gegen Verwechslung und angeblicher Performance unterscheiden. Concatenation mit dem Punkt-Operator baut den String explizit aus einzelnen Teilstücken zusammen, Interpolation bettet Variablen direkt in doppelte Anführungszeichen ein, Heredoc funktioniert wie Interpolation, erlaubt aber mehrzeiligen Text ohne Escaping von Anführungszeichen, und sprintf beziehungsweise vsprintf trennen Formatstring und Werte vollständig voneinander.
Jede dieser Formen hat einen legitimen Haupteinsatzzweck: Concatenation eignet sich für wenige, kurze Fragmente, Interpolation für einzeilige Texte mit wenigen eingebetteten Variablen, Heredoc für mehrzeilige Textblöcke wie E-Mail-Vorlagen oder SQL-Fragmente, und sprintf für Fälle, in denen Formatierung wie Nachkommastellen, Auffüllen mit Nullen oder Locale-abhängige Zahlenformate benötigt wird. Die Performance-Frage ist historisch deshalb so hartnäckig, weil frühe PHP-Versionen tatsächlich messbare Unterschiede zwischen diesen Formen aufwiesen, was sich mit modernen PHP-Versionen und OPcache stark relativiert hat.
2. Wie PHP Interpolation intern parst
Interpolierte Strings in doppelten Anführungszeichen werden nicht als reiner Text behandelt, sondern durchlaufen bereits beim Parsen einen eigenen Lexer-Zustand, der zwischen literalem Text und eingebetteten Variablen unterscheidet. Für die einfache Form $variable erkennt der Lexer das Dollarzeichen gefolgt von einem gültigen Bezeichner und ersetzt diesen Abschnitt durch den entsprechenden Wert, während für komplexere Ausdrücke wie Array-Zugriffe oder Objekteigenschaften die geschweifte Syntax {$obj->property} nötig ist, damit der Parser den Ausdruck eindeutig abgrenzen kann.
Intern kompiliert PHP einen interpolierten String zur Kompilierzeit in eine Folge von Opcodes, die im Kern denselben Concatenation-Operationen entsprechen, die auch ein expliziter Punkt-Operator erzeugen würde. Der vermeintliche Performance-Unterschied zwischen Interpolation und Concatenation existiert auf Opcode-Ebene in aktuellen PHP-Versionen praktisch nicht mehr, weil der Compiler beide Formen auf denselben internen Mechanismus abbildet, lediglich der Parsing-Aufwand zur Kompilierzeit unterscheidet sich geringfügig, was für die Laufzeit-Performance irrelevant ist.
3. Benchmark-Ansatz: Wie man String-Operationen valide misst
Ein naiver Benchmark, der einfach eine Million Iterationen in einer Schleife zählt und microtime davor und danach aufruft, misst häufig mehr Rauschen als echten Unterschied, weil ein einzelner Lauf durch Garbage Collection, CPU-Frequenzskalierung oder Hintergrundprozesse verzerrt wird. Valide misst man stattdessen mit mehreren Wiederholungen desselben Benchmarks, einem separaten Warmup-Durchlauf, der ausgeschlossen wird, sowie hrtime statt microtime, da hrtime eine monotone, nicht von Systemzeit-Anpassungen beeinflusste Uhr mit Nanosekunden-Auflösung liefert.
Ebenso wichtig ist es, das eigentliche Testszenario realistisch zu gestalten: Ein Benchmark, der denselben, immer identischen String tausendfach zusammensetzt, misst häufig nur, wie gut OPcache eine konstante Ausdrucksfolge optimieren kann, nicht das reale Verhalten bei tatsächlich variierenden Laufzeitwerten. Für belastbare Ergebnisse sollten die eingebetteten Werte deshalb bei jeder Iteration variieren, etwa über einen Zufallswert oder den Schleifenzähler selbst.
<?php
declare(strict_types=1);
/**
* Führt einen Benchmark mehrfach aus und gibt den Median der
* gemessenen Laufzeiten in Nanosekunden zurück, um Ausreisser durch
* GC-Pausen oder Systemrauschen abzufedern.
*
* @param callable $benchmark Der zu messende Codeblock
* @param int $iterationsPerRun Anzahl der Wiederholungen pro Messlauf
* @param int $runs Anzahl unabhängiger Messläufe
* @return float Der Median der Laufzeiten in Nanosekunden
*/
function benchmarkMedian(callable $benchmark, int $iterationsPerRun, int $runs): float
{
$durations = [];
for ($run = 0; $run < $runs; $run++) {
$start = hrtime(true);
for ($i = 0; $i < $iterationsPerRun; $i++) {
$benchmark($i);
}
$durations[] = hrtime(true) - $start;
}
sort($durations);
return $durations[(int) floor(count($durations) / 2)];
}
// Warmup-Durchlauf, der bewusst verworfen wird.
benchmarkMedian(fn (int $i) => "Wert: {$i}", 10_000, 1);
$result = benchmarkMedian(fn (int $i) => "Wert: {$i}", 100_000, 7);
4. Tatsächliche Zahlen und ihre Interpretation
Führt man einen solchen sauberen Benchmark auf einer aktuellen PHP-8.4-Installation mit aktiviertem OPcache aus, liegen Concatenation, Interpolation und Heredoc für einfache Fälle mit wenigen eingebetteten Variablen typischerweise innerhalb weniger Prozentpunkte auseinander, ein Unterschied, der in jedem realen Request neben Datenbankzugriffen und Netzwerklatenz vollständig verschwindet. sprintf zeigt dagegen einen konsistent messbaren, aber ebenfalls kleinen Overhead gegenüber den anderen drei Varianten, weil es den Formatstring zur Laufzeit parsen muss, statt bereits zur Kompilierzeit in Opcodes aufgelöst zu werden.
Dieser Overhead von sprintf wächst spürbar mit der Anzahl und Komplexität der Formatspezifizierer, etwa bei Nachkommastellen-Rundung, Auffüllung oder mehreren Positionsargumenten, bleibt aber selbst dann in der überwältigenden Mehrheit der Anwendungen weit unterhalb dessen, was durch eine einzige zusätzliche Datenbankabfrage oder einen zusätzlichen HTTP-Request verursacht wird. Die korrekte Schlussfolgerung aus solchen Zahlen ist fast immer, dass die Wahl der String-Methode eine Lesbarkeits- und Wartbarkeitsfrage bleibt, keine Performance-Frage, außer in eng abgegrenzten Hot Paths mit Millionen Aufrufen pro Sekunde.
5. Heredoc und Nowdoc: Eigenheiten und wann sie Lesbarkeit gewinnen
Seit PHP 7.3 unterstützt Heredoc eine sogenannte flexible Syntax, bei der die schließende Kennung eingerückt werden darf und PHP diese Einrückung automatisch von jeder Zeile des Inhalts entfernt, was Heredoc-Blöcke endlich sauber in eingerückten Code integrierbar macht, ohne die Lesbarkeit der umgebenden Methode zu zerstören. Vor dieser Änderung musste die schließende Kennung zwingend in Spalte eins stehen, was Heredoc in tief verschachteltem Code unhandlich und optisch inkonsistent machte.
Nowdoc, erkennbar an einfachen Anführungszeichen um die öffnende Kennung, verhält sich wie ein Heredoc, führt aber keinerlei Interpolation durch und eignet sich damit ideal für mehrzeilige Textblöcke, die selbst Dollarzeichen enthalten, etwa Shell-Skripte, reguläre Ausdrücke oder Vorlagen für andere Templatesprachen, bei denen eine versehentliche Interpolation zu schwer auffindbaren Fehlern führen würde. Heredoc gewinnt gegenüber Concatenation immer dann klar an Lesbarkeit, wenn mehr als etwa drei bis vier Zeilen oder mehrere eingebettete Werte zusammenkommen, weil der Lesefluss nicht mehr durch Punkt-Operatoren und Anführungszeichen unterbrochen wird.
<?php
declare(strict_types=1);
function buildInvoiceMail(string $customerName, float $amount, string $orderId): string
{
return <<<MAIL
Hallo {$customerName},
vielen Dank für Ihre Bestellung {$orderId}.
Der Rechnungsbetrag beträgt {$amount} Euro.
Mit freundlichen Grüßen
MAIL;
}
6. sprintf und vsprintf: Formatierung typsicher trennen
Der eigentliche Mehrwert von sprintf liegt nicht in der Zusammensetzung an sich, sondern in der klaren Trennung von Formatstring und Werten, kombiniert mit eingebauten Formatierungsregeln, die man mit reiner Concatenation oder Interpolation manuell nachbauen müsste. Ein Ausdruck wie sprintf('%05.2f', $value) übernimmt in einem einzigen, deklarativen Aufruf sowohl das Auffüllen mit führenden Nullen als auch die Begrenzung auf zwei Nachkommastellen, was mit number_format und Concatenation mehrere Zeilen erfordern würde.
Der zusätzliche Wert zeigt sich besonders bei Lokalisierung und bei Positionsargumenten der Form %1$s, mit denen sich dieselbe Variable mehrfach oder in unterschiedlicher Reihenfolge in verschiedenen Übersetzungen des Formatstrings verwenden lässt, ohne die eigentliche Werteliste umsortieren zu müssen. Genau für diesen Anwendungsfall, mehrsprachige Textbausteine mit variabler Wortstellung, ist sprintf gegenüber Interpolation praktisch alternativlos, selbst wenn es minimal langsamer ist.
7. OPcache-Einfluss auf String-Operationen und interned Strings
OPcache beeinflusst String-Performance auf zwei unabhängigen Ebenen. Erstens werden rein konstante Strings, also solche ohne jede Laufzeit-Variable, bereits beim Kompilieren zu einem einzigen Literal zusammengefasst, sodass ein rein statischer Heredoc-Block ohne eingebettete Variablen zur Laufzeit keinerlei Zusammensetzungsaufwand mehr verursacht, unabhängig davon, wie er im Quellcode geschrieben wurde. Zweitens nutzt PHPs Zend Engine sogenannte interned Strings, ein internes Pool-System, das identische String-Literale im gesamten Prozess nur einmal im Speicher vorhält und lediglich Referenzen darauf verteilt.
Für dynamisch zur Laufzeit zusammengesetzte Strings, wie sie bei Interpolation, Concatenation oder sprintf mit tatsächlich variierenden Werten entstehen, greift dieser Interning-Mechanismus naturgemäß nicht, da jeder neu erzeugte String potenziell einzigartig ist und separat alloziert werden muss. Der praktische Effekt ist, dass OPcache primär bei wiederholt identischen, statischen Textbausteinen hilft, etwa bei SQL-Grundgerüsten oder HTML-Vorlagenteilen, während die eigentliche Frage nach Concatenation versus Heredoc versus sprintf für tatsächlich dynamische Inhalte davon weitgehend unberührt bleibt.
8. Praxisempfehlung je nach Kontext
Für kurze, einzeilige Log-Nachrichten oder Fehlermeldungen mit ein bis zwei eingebetteten Werten ist Interpolation meist die lesbarste und damit richtige Wahl, weil sie den geringsten visuellen Overhead erzeugt. Für mehrzeilige Textblöcke wie E-Mail-Vorlagen, SQL-Statements oder JSON-Fixtures in Tests ist Heredoc fast immer die bessere Wahl gegenüber verketteten Concatenation-Zeilen, weil der Lesefluss dem tatsächlichen Zieltext entspricht, statt durch PHP-Syntax zerrissen zu werden.
Für Fälle mit tatsächlichem Formatierungsbedarf, etwa Geldbeträgen, Prozentangaben oder Positionsargumenten für Übersetzungen, bleibt sprintf trotz des kleinen Overheads die richtige Wahl, weil die Alternative, Formatierungslogik manuell mit string-Funktionen nachzubauen, sowohl fehleranfälliger als auch schwerer lesbar ist. Reine Concatenation mit dem Punkt-Operator eignet sich am ehesten für das schrittweise Aufbauen eines Strings über mehrere bedingte Codepfade hinweg, etwa beim Bauen eines dynamischen SQL-Query-Fragments in einer Schleife.
9. Häufige Fehleinschätzungen und die Microoptimization-Falle
Eine verbreitete Fehleinschätzung ist die Annahme, Concatenation mit dem Punkt-Operator sei grundsätzlich schneller als Interpolation, ein Glaubenssatz, der aus sehr alten PHP-Versionen stammt und in aktuellen Versionen mit funktionierendem OPcache empirisch nicht mehr haltbar ist. Ähnlich verbreitet ist die Annahme, sprintf sei für praktisch jeden Anwendungsfall zu langsam, obwohl der tatsächliche Overhead nur bei sehr vielen Millionen Aufrufen pro Sekunde überhaupt in eine relevante Größenordnung kommt.
Die eigentliche Microoptimization-Falle liegt darin, Zeit in die Wahl der String-Methode zu investieren, während echte Performance-Probleme fast immer an anderer Stelle liegen, etwa bei N+1-Datenbankabfragen, fehlenden Indizes oder unnötigen Netzwerk-Roundtrips. Ein realistisches Vorgehen misst zuerst mit einem Profiler wie Xdebug oder Blackfire, wo tatsächlich Zeit verloren geht, und widmet sich der String-Zusammensetzung nur dann, wenn ein Profiler sie tatsächlich als relevanten Anteil der Gesamtlaufzeit ausweist, was in der Praxis äußerst selten vorkommt.
| Methode | Lesbarkeit | Relative Performance | Einsatzempfehlung |
|---|---|---|---|
| Concatenation (.) | Mittel bei wenigen Teilen | Referenzwert | Schrittweiser Aufbau in Schleifen |
| Interpolation ("...") | Hoch bei einzeiligem Text | Nahezu identisch zu Concatenation | Kurze Log- und Fehlermeldungen |
| Heredoc | Hoch bei mehrzeiligem Text | Nahezu identisch zu Concatenation | E-Mail-Vorlagen, SQL-Blöcke |
| Nowdoc | Hoch, keine Interpolation | Nahezu identisch zu Concatenation | Shell-Skripte, Regex-Vorlagen |
| sprintf/vsprintf | Hoch bei Formatierungsbedarf | Leicht höherer Overhead | Geldbeträge, Positionsargumente |
Mironsoft
PHP-Modernisierung, Code-Qualität und Legacy-Refactoring
Gewachsener PHP-Code, der niemand mehr gern anfasst?
Wir modernisieren PHP-Codebasen auf aktuelle Sprachstandards, führen statische Analyse und Coding Standards ein und refactorn Legacy-Code Schritt für Schritt, ohne den laufenden Betrieb zu gefährden.
Legacy-Refactoring
Gewachsenen PHP-Code strukturiert und risikoarm modernisieren.
Code-Qualität etablieren
PHPStan, Coding Standards und CI-Checks nachhaltig im Team verankern.
Versions-Upgrade
PHP-Major-Version-Upgrades sicher planen und ohne Ausfallzeit umsetzen.
10. Zusammenfassung
String-Performance in PHP: Das Wichtigste auf einen Blick
Kein Performance-Sieger
Concatenation, Interpolation und Heredoc unterscheiden sich unter OPcache nur um wenige, praktisch irrelevante Prozentpunkte.
sprintf kostet minimal mehr
Der Laufzeit-Parser des Formatstrings verursacht messbaren, aber im Alltag meist bedeutungslosen Overhead.
Lesbarkeit entscheidet
Die Wahl der String-Methode sollte fast immer nach Lesbarkeit, nicht nach Performance getroffen werden.
Sauber messen statt raten
Ein valider Benchmark braucht Warmup, mehrere Läufe, hrtime und variierende statt konstanter Werte.