Vor der Fassade eines alten Gebäudes aus gelbem Backstein steht ein Baugerüst. Über den Fenstern wölben sich Bögen aus rotem Backstein, und die tief stehende Sonne wirft die Schatten des Gerüsts auf die Mauer.

Ende September 2026 hat 37signals eine Rust-Version von Campfire veröffentlicht, ihres selbst gehosteten Gruppenchats. Es ist ein einzelnes Binary, das die Ruby-on-Rails-Anwendung ersetzt und dabei ihre Datenbank, ihren Dateispeicher und ihre signierten Cookies weiterverwendet. Geschrieben haben es Coding Agenten, und der Mensch, der sie angeleitet hat, sagt, dass er den generierten Code nie angesehen hat.

Innerhalb einer Woche folgten Portierungen nach Go, Elixir, Express, Django und Laravel, wenige Tage später eine nach C. Anfang Oktober verglich die Benchmark-Tabelle im Repository von Campfire in Go sieben dieser Implementierungen. Rust lieferte die Seite eines Chatraums mit etwa 36.000 Requests pro Sekunde aus, Go mit etwa 3.900 und Rails mit etwa 240. Auf dieser Seite landete Laravel mit 164 auf dem letzten Platz.

Wenn du mit PHP arbeitest, kennst du den Reflex, den eine solche Tabelle auslöst. Müssen wir uns Sorgen machen? Braucht PHP einen Ahead-of-Time Compiler? Sollten Frameworks ihre heißen Codepfade in Rust ausliefern?

Das sind berechtigte Fragen, und ich komme auf sie zurück. Die interessanteste Frage, die dieses Experiment aufwirft, ist aber eine andere: Woher weiß irgendjemand, dass die Rust-Version korrekt ist?

Lies den Benchmark wie ein Testergebnis

Ein Benchmark ist ein Test, der statt eines Urteils eine Zahl meldet. Er verdient dieselben Fragen: Was deckt er ab, unter welchen Bedingungen, und was würde ihn scheitern lassen?

Die Zahlen für Campfire stammen aus dem eigenen Harness von 37signals: 16 gleichzeitige Clients, vier CPU-Kerne pro Anwendung, ein Rechner mit AMD Ryzen. Das ist sorgfältige Arbeit. Die erste Fassung der Tabelle verglich aber Portierungen, in die sehr unterschiedlich viel Arbeit geflossen war. Die Rust-Portierung hatte eine Reihe von Optimierungen hinter sich, jede davon vorher und nachher gemessen. Für die Portierungen nach Laravel und Django war dagegen der erklärte Zweck, herauszufinden, wie gut Agenten in der jeweiligen Umgebung ohne weitere Hilfe zurechtkommen.

Die Portierung nach Laravel zeigt, was das bedeutet. Innerhalb eines Tages ersetzte ein Pull Request nginx und PHP-FPM durch FrankenPHP und Laravel Octane, cachte gerenderte Nachrichtenlisten und tauschte an einigen Stellen die Hydrierung von Eloquent-Modellen gegen einfaches SQL. Nach den Messungen im Pull Request selbst stieg die Seite eines Chatraums von 55 auf etwa 2.000 Requests pro Sekunde, also um mehr als den Faktor 30. Weder die Sprache noch das Framework hatten sich geändert.

Diese Messungen liefen auf anderer Hardware, und ihr Autor sagt ausdrücklich, dass sie sich nicht mit der offiziellen Tabelle vergleichen lassen. Die Dokumentation der Rust-Portierung erlaubt es aber, die Effekte sauberer zu trennen.

Laut ihrem README von Ende September war schon eine direkte Übersetzung von Ruby nach Rust 3- bis 10-mal schneller als Rails. Alles darüber hinaus, bis zum Faktor 95 für die Seite eines Chatraums im Benchmark dieses README, kam aus Arbeit wie dem Cachen gerenderter Seitenteile, dem einmaligen Komprimieren statt bei jedem Request und einem Datenbankindex auf Raum und Erstellungszeit. Der Pull Request für Laravel hat genau denselben Index hinzugefügt.

Dann änderte sich die Tabelle selbst. Innerhalb weniger Tage übertrug 37signals, was die schnellen Portierungen schnell gemacht hatte, auf alle Implementierungen, auch auf Rails: das Cachen vollständiger Antworten sowie dieselben Datenbankabfragen und Indizes. Die Rails-Anwendung bekam außerdem neuere Versionen von Ruby und Puma, und der Pull Request für Laravel wurde übernommen. In der Tabelle vom 8. Oktober liefert Rails die Seite eines Chatraums mit etwa 4.100 Requests pro Sekunde aus, 17-mal so vielen wie zuvor, und Laravel mit etwa 3.900. Rust kommt auf etwa 106.000, Go auf etwa 53.000 und die neue Portierung nach C auf etwa 137.000. Der Faktor zwischen Rust und Rails schrumpfte von 150 auf 26.

Die erste Fassung der Tabelle vermischte also zwei Effekte mit unterschiedlichen Ursachen. Der eine ist die Sprache mit ihrer Laufzeitumgebung: Die direkte Übersetzung brachte den Faktor 3 bis 10, und mit demselben Caching und denselben Abfragen bedient Rust immer noch 26-mal so viele Requests wie Rails. Der andere ist das Ausführungsmodell und die Architektur: das Framework für jeden Request neu booten oder resident halten, alles rendern oder cachen, die Datenbank gut oder schlecht abfragen. Beide sind real, und eine ehrliche Diskussion muss sie auseinanderhalten.

Wo bleibt die Zeit?

Bevor du fragst, ob etwas in einer schnelleren Sprache neu geschrieben werden sollte, lohnt sich die Frage, wo seine Zeit verbraucht wird. Im PHP-Ökosystem lohnt es sich, drei Arten von Programmen zu unterscheiden.

Werkzeuge, die Code analysieren, verbringen ihre Zeit in ihrer eigenen Berechnung: Tokenisieren, Parsen, Typen ableiten, Syntaxbäume durchlaufen. Hier zahlt sich nativer Code aus, und er ist angekommen. Mago ist ein in Rust geschriebener Linter, Formatierer und statischer Analysator für PHP. PHPStan liefert inzwischen eine optionale Extension aus, die in C++ geschrieben ist, den größten Teil der Analyse-Engine neu implementiert und die Zeit, die PHPStan für die Analyse seines eigenen Quelltexts braucht, um 58 % verkürzt.

Die Dokumentation von PHPStan ist ungewöhnlich offen, was die Herkunft dieses Gewinns angeht. Ein Aufruf von PHP nach C++ kostet etwa so viel wie ein Aufruf innerhalb von PHP, deshalb ist eine native Schleife, die für jedes Element zurück nach PHP ruft, so langsam wie die PHP-Schleife, die sie ersetzt hat. Eine Portierung zahlt sich nur aus, wenn sie einen ganzen Aufrufbaum übernimmt und erst am Ende nach PHP zurückkehrt. Die ersten Versionen der Extension, ab Juli 2026 veröffentlicht, ersetzten eine Handvoll Klassen, die jeweils Millionen winziger Aufrufe auf sich zogen. Der große Schritt kam mit PHPStan 2.3 im Oktober 2026, das die Analyse-Engine mit etwa 350 Klassen als Ganzes in nativen Code verlagert hat. Regeln, Erweiterungen von Drittanbietern und die Schnittstelle für Erweiterungen sind in PHP geblieben und funktionieren unverändert.

Werkzeuge, die Code ausführen, verbringen die meiste Zeit in dem Code, den sie ausführen. PHPUnit ist ein solches Werkzeug: Wie lange eine Testsuite läuft, hängt vor allem davon ab, wie lange ihre Tests laufen. Den Testrunner in einer Systemsprache neu zu schreiben, würde den Teil beschleunigen, der nicht langsam ist. Die Hebel liegen woanders: dieselbe Arbeit nicht zweimal machen, Tests parallel ausführen und keine Tests ausführen, die von einer Änderung nicht betroffen sein können. Deshalb konzentriert sich meine Arbeit an PHPUnit auf diese Hebel. Seit PHPUnit 13.4 muss ein fokussierter Testlauf nicht mehr jede Testdatei laden, weil PHPUnit zwischenspeichern kann, welche Tests eine Datei enthält. Derzeit arbeite ich daran, Tests parallel auszuführen und Test Impact Analysis zu ermöglichen.

Anwendungen verbringen ihre Zeit im Ausführungsmodell, in I/O und in der Datenbank. Genau von dort kam der Faktor 30 in der Portierung nach Laravel. Den meisten PHP-Anwendungen fehlt nicht als Erstes ein schnellerer Interpreter. Auch ein Ahead-of-Time Compiler würde das Ausführungsmodell nicht ändern: Die Arbeit, die Anwendung hochzufahren, würde weiterhin für jeden Request wiederholt, nur schneller.

Woher weißt du, dass die schnelle Version korrekt ist?

Diesen Teil des Experiments mit Campfire finde ich am interessantesten, und er hat die wenigste Aufmerksamkeit bekommen.

Der Rust-Portierung wird nicht vertraut, weil jemand sie gereviewt hat. Ihr wird vertraut, weil sie mit der Rails-Anwendung verglichen wurde, systematisch und ohne Toleranz. Die Rails-Anwendung ist als Git-Submodul fest eingebunden, und das README nannte sie „the oracle for everything“, das Orakel für alles. Keine erwartete Ausgabe wurde von Hand geschrieben: Golden Data, Screenshots und Protokollaufzeichnungen stammten alle aus der Ausführung der Rails-Anwendung.

Ein Harness auf Basis von Playwright lässt beide Anwendungen nebeneinander mit identischen Startdaten und eingefrorenen Uhren laufen. Als die Portierung fertig war, deckte er 225 Bildschirmzustände über drei Browser Engines, vier Viewports sowie helles und dunkles Farbschema hinweg ab und verglich für jeden davon das HTML des Servers, das lebende DOM, den Accessibility Tree, jede Unterressource, die WebSocket Frames und einen pixelgenauen Screenshot. Bevor die erste Zeile Rust existierte, musste der Harness zeigen, dass die Rails-Anwendung mit sich selbst übereinstimmt, damit Nichtdeterminismus keine echten Unterschiede verdecken konnte.

Die native Extension von PHPStan wird nach demselben Muster überprüft. Jede Klasse mit einem nativen Gegenstück behält ihre PHP-Implementierung, markiert mit dem Attribut #[ShadowedByTurboExtension]. Ein differenzieller Test vergleicht die nativen Klassen mit ihren Zwillingen in PHP, und die gesamte Testsuite läuft mit geladener Extension. Zusätzlich wird ein kompletter Analyselauf zweimal aufgezeichnet, einmal mit den PHP-Klassen und einmal mit den nativen, bis hin zum Typ jedes Ausdrucks und zum Scope an jeder Anweisung, und beide Aufzeichnungen müssen identisch sein. Für den Parser, dessen Eingabebereich jeglicher PHP-Code ist, werden Tausende Dateien mit beiden Implementierungen geparst, und die serialisierten Syntaxbäume müssen Byte für Byte identisch sein. Die CI vergleicht außerdem per Reflection die Signaturen jeder nativen Klasse mit denen ihres Zwillings in PHP, und die Extension schaltet sich selbst ab, wenn ihre Version nicht zum Release von PHPStan passt.

Sogar der von einem Agenten erzeugte Performance Pull Request für die Portierung nach Laravel folgt diesem Muster. Sein Autor hat die Änderungen nicht gereviewt. Er hat sich auf die Testsuite verlassen und darauf, dass jede Seite Byte für Byte mit dem vorherigen Build übereinstimmt.

Keine dieser Techniken ist neu: Die Forschung zum Softwaretest nennt die zugrunde liegende Herausforderung das Orakelproblem: Wie entscheidest du, ob eine Ausgabe korrekt ist? Eine existierende Implementierung ist eines der besten Test Oracles, die es gibt. Zwei Implementierungen mit derselben Eingabe laufen zu lassen und ihre Ergebnisse zu vergleichen, ist Differential Testing, das William McKeeman 1998 beschrieben hat, um C-Compiler gegeneinander zu testen. Und aufzuzeichnen, was existierende Software tatsächlich tut, bevor du sie änderst, nannte Michael Feathers Characterization Testing.

Neu ist die Ökonomie: Laut dem README der Rust-Portierung von Ende September haben koordinierte Agenten sie in etwa einem Tag gebaut. Wenn sich eine Implementierung so schnell herstellen lässt, ist die Implementierung nicht mehr der teure Teil. Der Initiator der Rust-Portierung will den Rust-Code weder schreiben noch lesen; für ihn ist Rust das Ziel, in das seine Prompts übersetzt werden. Wenn es dorthin geht, ist der Quelltext, auf den es ankommt, die Spezifikation, gegen die der generierte Code geprüft wird. In der Praxis besteht diese Spezifikation aus einer Referenzimplementierung und einer Testsuite.

Rewrite oder Beschleunigung?

Es gibt zwei Wege, eine Referenzimplementierung zu nutzen, und sie unterscheiden sich darin, was hinterher aus dem Orakel wird.

Der erste ist der Rewrite. Die Rails-Anwendung diente als Orakel, während die Rust-Portierung gebaut wurde. Seit Parität erreicht ist, weicht die Rust-Portierung bewusst von Rails ab, wo immer sie dadurch schneller oder besser wird. Jede Abweichung ist dokumentiert, und der Harness maskiert sie oder setzt sie auf eine Allowlist. Ab diesem Punkt ist die Spezifikation nicht mehr die Rails-Anwendung. Sie ist die Rails-Anwendung abzüglich einer wachsenden Liste von Ausnahmen, und sobald Rails abgeschaltet wird, ist sie das, was die Tests festhalten.

Der zweite ist die Beschleunigung: den Code in PHP schreiben, die messbar langsamen Teile finden, nur diese in nativen Code portieren und beides behalten. PHPStan geht diesen Weg. Ebenso graphql_accelerator, eine Extension in Rust für den Parser von webonyx/graphql-php. Sie forkt die Bibliothek nicht. Stattdessen stellt sie native Implementierungen des Parsers von graphql-php und der Knotenklassen seines Syntaxbaums bereit, sodass die PHP-Versionen dieser Klassen nie per Autoload geladen werden, und sie meldet eine 11- bis 16-fache Beschleunigung.

Ihre Korrektheit wird geprüft, indem die PHPUnit Tests von graphql-php für GraphQL\Language, wozu Lexer, Parser, Printer und Visitor gehören, mit geladener Extension ausgeführt werden: 227 Tests mit 14.299 Assertions; zwei der Tests werden übersprungen. Ihr Autor sagt offen, dass der Code größtenteils mit LLM-basierten Werkzeugen erzeugt wurde und dass die Entwicklung davon getrieben war, die ursprünglichen Tests grün zu halten.

Der Weg der Beschleunigung hält das Orakel am Leben. Die PHP-Implementierung bleibt die Referenz, der Fallback, wenn die Extension nicht verfügbar ist, und der Maßstab für jede Änderung am nativen Code. Für mich ist das das stärkste Argument für diesen Weg: Es ist der, auf dem du weiter überprüfen kannst. Die Regeln von PHPStan für neue native Portierungen machen denselben Punkt aus der anderen Richtung. Eine Portierung, die bei identischer Ausgabe keinen messbaren Gewinn nachweisen kann, wird zurückgenommen, denn „unproven native code is pure maintenance debt“: Unbewiesener nativer Code ist reine Wartungslast.

Was braucht es in der Praxis, um das Orakel am Leben zu halten?

  • Die gemeinsame Testsuite läuft in mindestens zwei Konfigurationen, mit und ohne nativen Code, als Matrix in der CI. Ein Test, der übersprungen wird, weil eine Extension fehlt, vergleicht nichts.
  • Dazu kommen Prüfungen, die nur für den nativen Code sinnvoll sind, etwa der Vergleich der Signaturen zwischen einer nativen Klasse und ihrem Zwilling in PHP, den PHPStan durchführt.
  • Beispielbasierte Tests decken nur die Eingaben ab, an die jemand gedacht hat. Generierte Eingaben, die beiden Implementierungen vorgesetzt werden und deren Ergebnisse verglichen werden, decken den Rest ab. Hier kommen Property-Based Testing und Fuzzing ins Spiel, und die Referenzimplementierung macht die Property trivial: gleiche Eingabe, gleiche Ausgabe. Die Rust-Portierung von Campfire hat genau das für ihre Rich Text Pipeline getan, mit einem Korpus von 658 Fällen, 400 davon durch Fuzzing erzeugt.

Nativen Code mit PHP-Paketen auszuliefern, wirft eine zweite Frage auf: Woher kommen die Binaries? PHPStan liefert bereits vorgebaute Binaries seiner Extension für die gängigen Plattformen in seinem Composer-Paket aus. PIE, der PHP Installer for Extensions, kann vorgebaute Binaries herunterladen, und Packagist hat als längerfristige Richtung angekündigt, unveränderliche Build-Artefakte mit SLSA Build Provenance und Sigstore-Attestierungen zu hosten, die Composer überprüft. Nativer Code in PHP-Paketen macht Provenienz, reproduzierbare Builds und SBOMs von etwas Wünschenswertem zu Anforderungen. Das verdient einen eigenen Artikel.

Es überleben die Tests, die an der Schnittstelle hängen, die du behältst

Das bringt mich zur Frage im Titel: Stell dir vor, deine Anwendung oder eine Bibliothek, die du pflegst, würde morgen neu implementiert. Welche deiner Tests könnten unverändert gegen die neue Implementierung laufen?

Die Antwort hängt davon ab, was der Ersatz bewahrt. graphql_accelerator bewahrt die PHP-Klassen von graphql-php, ihre Namen und ihre Methoden. Deshalb konnten die Tests von graphql-php, die auf der Ebene dieser Klassen testen, unverändert gegen den nativen Code laufen. Die Rust-Portierung von Campfire bewahrt viel weniger: HTTP, das Datenbankschema, die Cookies und das WebSocket-Protokoll. Nur Tests auf dieser Ebene hätten diesen Rewrite überlebt, und genau auf dieser Ebene arbeitet ihr Harness.

Die genaue Antwort lautet also: Es überleben die Tests, die an die Schnittstelle gebunden sind, die du behältst. Tests, die mit Mock-Objekten prüfen, welche Methoden aufgerufen werden, oder die internen Zustand inspizieren, sind an eine bestimmte Implementierung gebunden. Sie können nützlich sein, während du an dieser Implementierung arbeitest, aber sie können dir nicht sagen, ob eine andere korrekt ist.

Das ist kein Argument gegen Unit Tests. Es ist ein Argument dafür, zu wissen, welche deiner Tests Verhalten spezifizieren und welche nur beschreiben, wie der aktuelle Code zufällig funktioniert. Die Testsuite von PHPUnit selbst ist ein Beispiel. Ihre End-to-End Tests sind PHPT-Dateien, die den Testrunner mit bestimmten Argumenten starten und seine vollständige Ausgabe mit einer Erwartung vergleichen. Sie hängen kaum von mehr ab als dem Einstiegspunkt des Runners, seinen Argumenten und seiner Ausgabe, und würden deshalb jeden Rewrite überleben, der diese beibehält.

Du musst nicht die Sprache wechseln, um darauf zu stoßen. Eine Laravel-Anwendung von PHP-FPM auf Octane umzustellen, ändert keine Geschäftslogik, aber das Ausführungsmodell: Langlebige Worker behalten Zustand über Requests hinweg, den PHP-FPM nach jedem Request verworfen hat. Der Performance Pull Request für die Portierung nach Laravel zeigt, was das bedeutet. Einer seiner Commits setzt den aktuellen Benutzer zu Beginn jedes Requests zurück, weil Seiten für Gäste sonst mit dem Benutzer des vorherigen Requests gerendert worden wären. Die Tests, die einen solchen Fehler finden, sprechen die Anwendung über ihre HTTP-Schnittstelle an, und zwar über mehr als einen Request hinweg.

Es gibt allerdings einen Haken. Die meisten funktionalen Tests in PHP gehen nie durch die Laufzeitumgebung: Sie übergeben ein Request-Objekt innerhalb des Testprozesses an den Kernel des Frameworks. Der Lebenszyklus zwischen zwei solchen Requests ist dann das, was das Testwerkzeug implementiert, und nicht das, was der Server tut, auf den du deployst. Der Test Client von Symfony etwa bootet den Kernel standardmäßig für jeden Request neu, was eher PHP-FPM ähnelt als einem Worker, der die Anwendung im Speicher hält.

Wenn du die Laufzeitumgebung wechselst, muss also zumindest ein Teil deiner Tests gegen die Laufzeitumgebung laufen, auf die du tatsächlich deployst. Und jede Laufzeitumgebung, die du unterstützt, fügt dieser Matrix eine Konfiguration hinzu; allein Laravel Octane unterstützt vier Anwendungsserver.

Ein Harness beweist Übereinstimmung, nicht Korrektheit

Ein Paritäts-Harness beweist, dass zwei Implementierungen übereinstimmen. Er beweist nicht, dass eine von ihnen korrekt ist.

Auch hier ist die Rust-Portierung von Campfire lehrreich. Ihr README listet Fehler der Rails-Anwendung auf, die die Portierung absichtlich behebt, darunter eine Stored Cross-Site Scripting-Schwachstelle in der Art, wie URLs in Nachrichten zu Links werden. Ein Harness, der gegen Rails vergleicht, kann solche Fehler nicht melden; er kann sie nur getreu reproduzieren. Sie zu beheben hieß, vom Orakel abzuweichen, bewusst und dokumentiert.

Ein Harness verschafft auch niemandem ein Verständnis des Codes, der ihn besteht. Der Initiator der Portierung sagt, dass er den Rust-Code nicht gelesen hat. Für eine Chat-Anwendung mit einer lebendigen Referenzimplementierung und einem gründlichen Harness mag das ein akzeptabler Kompromiss sein. Sobald die Referenzimplementierung aber weg ist, ist der Harness die einzige verbliebene Beschreibung dessen, was die Software tun soll, und er wurde geschrieben, um zu vergleichen, nicht um zu erklären. Was passiert, wenn Code schneller entsteht, als er verstanden wird, habe ich schon an anderer Stelle beschrieben.

Auf dem Weg der Beschleunigung bleibt das Risiko einer Abweichung überall dort, wo die Tests schweigen. graphql_accelerator besteht 14.299 Assertions, und sein Autor nennt ihn trotzdem nicht produktionsreif. Das ist die richtige Einschätzung, denn eine grüne Testsuite ist genau so stark wie die Testsuite.

Diese Stärke lässt sich messen. Der Harness von Campfire musste zeigen, dass Rails mit sich selbst übereinstimmt, bevor er über Rust urteilen durfte: ein Test des Determinismus des Orakels. Die Empfindlichkeit des Orakels verdient dieselbe Aufmerksamkeit. Mutation Testing der Referenzimplementierung in PHP sagt dir, wie viele absichtlich eingebaute Fehler deine Tests finden. Eine Testsuite, die nicht bemerkt, wenn die Referenzimplementierung kaputt ist, wird auch nicht bemerken, wenn die native kaputt ist. Bevor du einer Testsuite als Orakel vertraust, teste das Orakel.

Tests sind allerdings nicht die einzige deterministische Prüfung. Ein Test deckt die Eingaben ab, an die jemand gedacht hat; ein Typprüfer deckt jeden Pfad durch den Code ab, begrenzt nur durch das, was sein Typsystem ausdrücken kann. Ein Artikel auf heise.de hat kürzlich argumentiert, dass genau das Rust als Zielsprache für Agenten attraktiv macht: Wo die Tests an ihre Grenzen stoßen, übernimmt der Compiler.

PHP prüft seine Typdeklarationen zur Laufzeit, nicht bevor das Programm läuft. Sein Gegenstück zu diesem Compiler ist die Kombination aus strikten Typdeklarationen und statischer Analyse mit PHPStan, Psalm oder Mago in ihren strengsten Einstellungen. Diese Werkzeuge sind schwächer als ein Compiler: Sie vertrauen Annotationen und müssen aufgeben, wo Code dynamisch ist. Für Code, den niemand liest, ist diese zweite deterministische Prüfung trotzdem keine Stilfrage. Sie ist die einzige, die alle Eingaben abdeckt statt einer Stichprobe, und ich habe an anderer Stelle beschrieben, wo Tests aufhören und Typen anfangen.

PHP muss kein Wettrennen um Durchsatz gewinnen

Sollte sich die PHP-Community über eine Benchmark-Tabelle sorgen, in der Rust immer noch mehr als 25-mal so schnell ist wie Laravel? Ich glaube nicht, dass PHP ein Wettrennen um Durchsatz gegen Rust gewinnen muss. Es hat das nie versucht. Es muss schnell genug bleiben, dass es die vernünftige Entscheidung ist, eine Codebasis in PHP zu behalten oder ein neues Projekt in PHP zu beginnen.

Ein guter Teil des ursprünglichen Abstands in dieser Tabelle lag im Ausführungsmodell und in der Architektur, nicht in der Sprache, wie die Tabelle selbst wenige Tage später gezeigt hat. Wo die eigene Berechnung eines Werkzeugs dominiert, ist nativer Code die richtige Antwort, und der Weg der Beschleunigung zeigt, wie er sich einführen lässt, ohne die PHP-Implementierung als Referenz aufzugeben.

Das Kapital, das jede dieser Entscheidungen erst möglich macht, ist aber nicht der Code. Es ist die Spezifikation, gegen die du eine Implementierung prüfen kannst: eine Referenzimplementierung und Tests, die an die Schnittstellen gebunden sind, die du behalten willst. Mit ihr kannst du von PHP-FPM auf Octane umsteigen, eine native Extension akzeptieren oder einen Agenten eine Portierung bauen lassen und wissen, ob das Ergebnis korrekt ist. Ohne sie hilft dir kein Compiler.

Deshalb hier eine kleine Übung: Wähle zufällig zehn Tests aus deiner Testsuite aus. Frag dich bei jedem, ob er gegen eine Neuimplementierung des getesteten Codes noch aussagekräftig wäre. Die, bei denen das so ist, sind deine Spezifikation; die anderen sind Baugerüst. Es lohnt sich zu wissen, wie viele du von jeder Sorte hast.