- Das CPython-Projekt hat kürzlich eine neue Implementierungsstrategie für den Bytecode-Interpreter eingeführt. Erste Ergebnisse zeigten auf verschiedenen Plattformen im Durchschnitt eine Leistungssteigerung von 10–15 %.
- Diese Leistungssteigerung war jedoch hauptsächlich das Ergebnis der Umgehung eines Regressionsproblems in LLVM 19. Im Vergleich mit besseren Referenzen (z. B. GCC, clang-18, LLVM 19 mit bestimmten Tuning-Flags) schrumpfte der Leistungsgewinn auf 1–5 %.
Leistungsergebnisse
- Mehrere Builds des CPython-Interpreters wurden mit verschiedenen Compilern und Konfigurationsoptionen benchmarked. Getestet wurde auf einem Intel-Server und einem Apple M1 MacBook Air.
- Alle Builds verwendeten LTO und PGO. Als Referenz wurde
clang18 verwendet, zusammen mit dem in pypeformance/pyperf compare_to gemeldeten Durchschnitt.
- Vergleich der Compiler-Leistung
- Apple M1 MacBook Air:
- clang18: Referenz
- clang19: 1,12× langsamer
- clang19.taildup: 1,02× langsamer
- clang19.tc: 1,00× langsamer
- gcc: N/A
- Der Tail-Call-Interpreter zeigte im Vergleich zu clang-18 weiterhin einen Geschwindigkeitsvorteil, aber der Leistungsabfall beim Wechsel zu clang-19 war deutlich ausgeprägter.
LLVM-Regression
Kurzer Hintergrund
- Traditionelle Bytecode-Interpreter bestehen aus einer
switch-Anweisung innerhalb einer while-Schleife. Die meisten Compiler kompilieren switch als Sprungtabelle.
- Moderne C-Compiler unterstützen das Muster, die Adresse von Labels zu nehmen und sie als „computed goto“ zu verwenden. CPython nutzte dieses Muster bis zu den Tail-Call-Arbeiten.
LLVM-19-Regression
- LLVM 19 schränkte den Tail-Duplication-Pass ein, sodass die Duplizierung beendet wird, wenn die IR-Größe einen bestimmten Grenzwert überschreitet. Dadurch wurden in CPython alle Dispatch-Sprünge zusammengeführt, was den Zweck der auf berechneten
goto basierenden Implementierung vollständig zunichtemachte.
Weitere Auffälligkeiten
- Es ist zwar sicher, dass die Änderung an der Logik zur Tail-Call-Duplizierung die Regression verursacht hat, aber das Ausmaß der Regression lässt sich damit nicht vollständig erklären.
- Auf modernen Prozessoren sind Geschwindigkeitsgewinne von 2–4 % üblicher.
Ist computed goto notwendig?
- Der Benchmark
clang19.nocg behauptet, schneller als clang19 zu sein. Das zeigt, dass der Compiler mit einem switch-basierten Interpreter dieselben Optimierungen durchführen kann.
Fix
- LLVM Pull Request 114990 hat die Regression behoben. Dieser Fix stellt die erwartete Leistung wieder her.
Rückblick
Über Benchmarking
- Bei der Optimierung von Systemen werden Benchmarks und Benchmarking-Methoden erstellt, um vorgeschlagene Änderungen zu bewerten.
- Benchmarks erfordern zusätzliche Annahmen und Überzeugungen, um bestimmte Datenpunkte zu verallgemeinern.
Referenzwert
- Wenn eine neue Lösung oder Methode vorgeschlagen wird, ist es üblich, sie mit dem „aktuell besten bekannten Ansatz“ zu vergleichen.
Über Software Engineering
- Softwaresysteme sind komplex, miteinander verbunden und verändern sich schnell.
- Optimierende Compiler stehen in einem Spannungsverhältnis dazu, den Code zu optimieren und zugleich die Absicht des Programmierers zu respektieren.
Optimierende Compiler
- Das Attribut
musttail steht für eine neue Art compilerbezogener Funktion im Zusammenhang mit Optimierung. Es könnte einen leistungsfähigeren Stil zum Schreiben performancekritischen Codes ermöglichen.
Noch ein Wort zu nix
nix war in diesem Projekt äußerst nützlich. Es hat stark dabei geholfen, mehrere Versionen des Python-Interpreters zu verwalten und zu bauen.
1 Kommentare
Hacker-News-Kommentare
Hallo. Ich bin der Autor des PRs, der einen Tail-Calling-Interpreter in CPython eingeführt hat
Benchmarking ist wirklich eine Aufgabe, die sehr schwer gut zu machen ist
Ich zolle dem Autor Anerkennung dafür, dass er die Wahrheit hinter diesem Problem aufgedeckt hat
Das ist ein gutes Beispiel dafür, dass C keine „maschinennahe“ Sprache ist
Durch Anpassungen daran, wie der Compiler die Schleife organisiert, ist der Tail-Call-Interpreter nicht so effektiv wie angekündigt
Die Performance eines Python-Builds zu bewerten, ist sehr schwierig
Verwandte Diskussionen:
Ein großartiger Artikel
Ich habe vor Kurzem Benchmarks von Python 3.9 bis 3.13 durchgeführt
Ich frage mich, wie diese Optimierung mit Tail-Call-Optimierung zusammenhängt