15 Punkte von darjeeling 2026-03-29 | Noch keine Kommentare. | Auf WhatsApp teilen

Zentrale Ergebnisse

Plattform JIT-Leistungssteigerung (vs. Tail-Calling-Interpreter)
macOS AArch64 +11~12%
x86_64 Linux +5~6%

Benchmark-Spanne: von Fällen mit 20% Verlangsamung bis zu Fällen mit über 100% Beschleunigung (unpack_sequence-Mikrobenchmark ausgenommen)

  • Ziel erreicht: Das Ziel für 3.15 (5% Verbesserung) wurde mehr als ein Jahr früher erreicht
  • Unterstützung für Free-Threading: Noch nicht abgeschlossen, Arbeit dafür läuft mit Ziel 3.15/3.16

Wichtige Lehren

  1. Der Wegfall eines Sponsors = Krise → Umstellung auf Community-getriebene Entwicklung
    Obwohl der wichtigste Sponsor des Faster-CPython-Teams 2025 ausgestiegen ist, haben freiwillige Beiträge aus der Community die Zahl der Mitwirkenden sogar erhöht und zu Ergebnissen geführt.

  2. Komplexe Probleme in kleine Teile zerlegen
    Selbst bei einem Projekt mit hoher Einstiegshürde wie dem JIT konnten durch die Zerlegung in Einzelschritte plus klare Anleitungen auch Nicht-Spezialisten mit C-Erfahrung beitragen.

  3. Ein geringerer Bus-Faktor ist ein Maßstab für ein gesundes Projekt
    Die Zahl der Mitwirkenden in der mittleren Stufe (Optimizer) stieg von 2 auf 4, und auch Nicht-Core-Entwickler wurden zu wichtigen Beitragsleistenden.

  4. Messinfrastruktur verändert das Entwicklungstempo
    Ein System, das täglich die JIT-Leistung meldet (doesjitgobrrr.com), war entscheidend, um Regressionen früh zu erkennen und die Motivation hochzuhalten.

  5. Austausch zwischen Communities steigert die technische Kompetenz
    Der Austausch mit dem PyPy-Team und informelle Gespräche mit Compiler-Entwicklern führten zu spürbaren technischen Fortschritten.


Hintergrund und Ergebnisse

[IMG] JIT-Leistungsdiagramm (Stand: 17. März 2026, niedriger = schneller als der Interpreter)
(JIT-Leistung am 17. März 2026. Je niedriger, desto schneller im Vergleich zum Interpreter. Bildquelle: doesjitgobrrr.com)

Das sind gute Nachrichten. Unter macOS AArch64 wurde das (sehr bescheidene) Leistungsziel des CPython-JITs mehr als ein Jahr früher erreicht, unter x86_64 Linux mehrere Monate früher. Das Alpha-JIT von 3.15 ist auf macOS AArch64 etwa 11~12% schneller als der Tail-Calling-Interpreter und auf x86_64 Linux 5~6% schneller als der Standard-Interpreter. Diese Werte sind geometrische Mittelwerte und vorläufig. In der Praxis reicht die Spanne von 20% langsamer bis über 100% schneller(unpack_sequence-Mikrobenchmark ausgenommen). Eine echte Unterstützung für Free-Threading gibt es noch nicht, sie ist aber für 3.15/3.16 eingeplant. Das JIT ist nun wieder auf Kurs.

Wie schwer das war, lässt sich kaum übertreiben. Es gab Zeiten, in denen ernsthaft fraglich war, ob das JIT-Projekt überhaupt eine nennenswerte Beschleunigung liefern könnte. Rückblickend brachte das ursprüngliche CPython-JIT praktisch keinen Geschwindigkeitsgewinn. Vor 8 Monaten habe ich einen Rückblick zum JIT veröffentlicht, in dem ich festhielt, dass das ursprüngliche CPython-JIT in 3.13 und 3.14 oft sogar langsamer als der Interpreter war. Damals verlor das Faster-CPython-Team auch die Unterstützung seines wichtigsten Sponsors. Mich persönlich traf das als Freiwilligen nicht direkt, aber Kolleginnen und Kollegen dort schon, und zeitweise wirkte die Zukunft des JIT unsicher.

Was hat sich also gegenüber 3.13 und 3.14 verändert? Ich werde hier keine Heldengeschichte darüber erzählen, wie wir das JIT mit bloßer Genialität vor dem Scheitern gerettet hätten. Ehrlich gesagt beruht der größte Teil des jetzigen Erfolgs auf Glück. Der richtige Zeitpunkt, der richtige Ort, die richtigen Menschen, die richtigen Entscheidungen. Ich bin ernsthaft überzeugt, dass es ohne auch nur eine der zentralen JIT-Mitwirkenden Savannah Ostrowski, Mark Shannon, Diego Russo, Brandt Bucher oder mich nicht möglich gewesen wäre. Um andere aktive JIT-Beitragende nicht zu übergehen, nenne ich noch einige weitere: Hai Zhu, Zheaoli, Tomas Roun, Reiden Ong, Donghee Na und sicher noch andere, die ich hier vergesse.

Ich möchte über einen Teil des JIT sprechen, der selten erwähnt wird: die Menschen und ein bisschen Glück. Wer sich für die technischen Details interessiert, findet sie hier.


Teil 1: Community-getriebenes JIT

Das Faster-CPython-Team verlor 2025 seinen wichtigsten Sponsor. Ich habe daraufhin sofort die Idee einer Community-getriebenen Betreuung vorgeschlagen. Damals war ziemlich unklar, ob das funktionieren würde. Das JIT-Projekt gilt nicht als besonders einsteigerfreundlich. Historisch gesehen brauchte man viel Vorwissen.

Beim CPython-Core-Sprint in Cambridge traf sich das JIT-Kernteam und erstellte einen Plan für ein bis 3.15 um 5% schnelleres JIT und bis 3.16 um 10% schnelleres JIT samt Free-Threading-Unterstützung. Weniger beachtet, aber für die Gesundheit des Projekts essenziell, war dabei die Senkung des Bus-Faktors. In allen drei Phasen des JITs (Region Selector im Frontend, Optimizer im Middle-End und Code Generator im Backend) wollten wir jeweils mehr als zwei aktive Maintainer haben.

Früher gab es im JIT-Middle-End nur 2 aktive, wiederholt beitragende Personen. Heute gibt es dort 4 aktive wiederholte Mitwirkende, und zwei Nicht-Core-Entwickler (Hai Zhu und Reiden) sind zu kompetenten und geschätzten Mitgliedern geworden.

Was beim Gewinnen neuer Leute wirksam war, waren ganz klassische Software-Engineering-Praktiken: komplexe Probleme in handhabbare Teile zerlegen. Brandt begann damit zuerst in 3.14 und eröffnete mehrere Mega-Issues, die JIT-Optimierungen in einfache Aufgaben aufteilten. Zum Beispiel in der Art von „Optimiere eine einzelne Anweisung im JIT“. Ich habe Brandts Idee aufgegriffen und auch in 3.15 angewendet. Glücklicherweise bekam ich die leichtere Arbeit: Issues, bei denen Interpreter-Anweisungen in eine Form überführt werden, die sich leicht optimieren lässt. Um neue Beitragende zu ermutigen, habe ich sehr ausführliche Anleitungen zusammengestellt, die direkt umsetzbar waren. Außerdem habe ich die Arbeitseinheiten klar voneinander abgegrenzt. Das scheint geholfen zu haben. Insgesamt 11 Mitwirkende, mich eingeschlossen, arbeiteten an diesem Issue und wandelten fast den gesamten Interpreter in eine JIT-optimizerfreundliche Form um. Der Kern war, das JIT von einem undurchsichtigen Block in etwas zu zerlegen, zu dem auch C-Programmierer ohne JIT-Erfahrung beitragen können.

Weitere wirksame Methoden: Menschen ermutigen und große wie kleine Erfolge feiern. Jeder JIT-PR hatte ein klares Ergebnis, und ich denke, das gab den Leuten Orientierung.

Die gemeinschaftliche Optimierungsarbeit zahlte sich aus. Das JIT entwickelte sich unter x86_64 Linux von 1% schneller zu 3~4% schneller (siehe blaue Linie unten):

[IMG] JIT-Leistung vs. Interpreter während der Community-Optimierungsphase
(Bildquelle: doesjitgobrrr.com)


Teil 2: Glückliche Entscheidungen

Trace Recording

Noch einmal: Ich glaube, vieles davon war Glück. Beim CPython-Core-Sprint in Cambridge überzeugte mich Brandt, das JIT-Frontend als Tracing-Ansatz neu zu schreiben. Anfangs war ich nicht begeistert, aber aus einer Art Trotz dachte ich: Schreiben wir es eben um, „um zu beweisen, dass es nicht funktioniert“.

Der erste Prototyp lief nach 3 Tagen, aber es dauerte einen Monat, bis er die Test-Suite bestand und wirklich korrekt JIT-Kompilierung machte. Die ersten Ergebnisse waren furchtbar: auf x86_64 Linux etwa 6% langsamer. Gerade als ich aufgeben wollte, passierte ein glücklicher Zufall: Ich hatte Marks Vorschlag falsch verstanden.

Mark hatte vorgeschlagen, eine Dispatch-Tabelle per Threading in den Interpreter einzubinden, sodass es zwei Dispatch-Tabellen im Interpreter gibt: eine für den normalen Interpreter und eine fürs Tracing. Ich verstand das jedoch falsch und baute eine extremere Variante: Statt Tracing-Versionen normaler Anweisungen gab es nur eine einzige Anweisung fürs Tracing, auf die alle Anweisungen in der zweiten Tabelle zeigten. Das erwies sich als sehr gute Entscheidung. Der ursprüngliche Ansatz mit zwei vollständigen Tabellen verdoppelte die Größe des Interpreters, verursachte Code-Bloat und entsprechend einen natürlichen Leistungsverlust. Mit nur einer einzigen Anweisung und zwei Tabellen wuchs der Interpreter praktisch nur um eine Anweisung, und der Basis-Interpreter blieb sehr schnell. Diesen Mechanismus nenne ich liebevoll Dual Dispatch.

Eine Zahl, die zeigt, wie wichtig Trace Recording war: Die JIT-Code-Coverage stieg um 50%. Das bedeutet, dass alle künftigen Optimierungen (vereinfacht gesagt) sonst um 50% weniger wirksam gewesen wären.

Danke an Brandt und Mark dafür, dass sie mich dazu gebracht haben, diese großartige Lösung eher zufällig zu entdecken.

Eliminierung von Referenzzählungen

Eine weitere glückliche Entscheidung war der Versuch, Referenzzählungen zu eliminieren. Ursprünglich war das Arbeit von Matt Page am CPython-Bytecode-Optimizer. Mir fiel auf, dass trotz der Arbeit am Bytecode-Optimizer im JIT-kompilierten Code nach jeder Dekrementierung eines Referenzzählers noch ein Branch übrig blieb. Ich dachte mir: „Was wäre, wenn man diesen Branch entfernt?“, ohne zu wissen, wie viel das bringen würde. Schon ein einzelner Branch war tatsächlich ziemlich teuer, und wenn jede Python-Anweisung einen oder mehrere Branches enthält, summiert sich das.

Ein weiterer Glücksfall war, wie leicht sich das parallelisieren ließ und wie gut es sich eignete, Menschen den Interpreter und das JIT näherzubringen. Das war die wichtigste Optimierung, auf die ich Leute für das Python-3.15-JIT angesetzt habe. Es war größtenteils ein manueller Refactoring-Prozess, bot den Leuten aber eine Gelegenheit, die Kernteile des JIT kennenzulernen, ohne von ihnen überwältigt zu werden.


Teil 3: Ein großartiges Team

Wir haben ein großartiges Infrastrukturteam. Tatsächlich ist es eine Person. Genau genommen besteht unser „Team“ derzeit aus 4 Maschinen, die in Savannahs Kleiderschrank laufen. Trotzdem hat Savannah für das JIT Arbeit geleistet, die einem ganzen Infrastrukturteam entspricht. Ohne eine Möglichkeit, Leistungszahlen zu melden, hätte das JIT sich nie so schnell weiterentwickeln können. Die täglichen Ergebnisse der JIT-Läufe waren ein Gamechanger im Feedback-Loop. Sie halfen dabei, Regressionen in der JIT-Leistung aufzuspüren, und machten sichtbar, dass unsere Optimierungen tatsächlich funktionierten.

Mark ist technisch herausragend. Ich sage dazu nicht mehr, weil das Internet ihn ohnehin schon genug lobt :).

Auch Diego ist großartig. Er ist für das JIT auf ARM-Hardware zuständig und hat kürzlich damit begonnen, das JIT profilerfreundlicher zu machen. Wie schwierig dieses Problem ist, kann man kaum übertreiben.

Brandt hat die ursprüngliche Grundlage für das Machine-Code-Backend gelegt. Ohne das hätten neue Beitragende Assembler schreiben müssen, was vermutlich noch mehr Leute abgeschreckt hätte.


Teil 4: Mit Menschen reden

Ich möchte den Wert davon betonen, mit anderen zu sprechen und Ideen auszutauschen.

Danke an CF Bolz-Tereick, der mir viel über PyPy beigebracht hat. Ich habe mir über mehrere Monate den PyPy-Quellcode angesehen, und ich glaube, das hat mich insgesamt zu einem besseren JIT-Entwickler gemacht. CF hat immer sehr freundlich geholfen, wenn ich Unterstützung brauchte.

Mit Max Bernstein bin ich in einem lockeren Compiler-Chat, und ohne diesen Austausch hätte ich wahrscheinlich schon vor langer Zeit die Motivation verloren. Max ist ein produktiver Autor und ein umgänglicher Compiler-Experte.

Ideen existieren nicht im luftleeren Raum. Ich denke, dass ich durch den Umgang mit Compiler-Entwicklern über längere Zeit besser darin geworden bin, JITs zu schreiben. Zumindest hat mir der Blick auf PyPy den Horizont erweitert!


Fazit

Menschen sind wichtig. Und mit ein bisschen Glück dazu gilt: JIT go brrr.

Noch keine Kommentare.

Noch keine Kommentare.