Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 3146 – Zusammenführung von Unladen Swallow in CPython

Autor:
Collin Winter <collinwinter at google.com>, Jeffrey Yasskin <jyasskin at google.com>, Reid Kleckner <rnk at mit.edu>
Status:
Zurückgezogen
Typ:
Standards Track
Erstellt:
01-Jan-2010
Python-Version:
3.3
Post-History:


Inhaltsverzeichnis

Rücknahme eines PEP

Da Unladen Swallow den Weg des Norwegian Blue gegangen ist [1] [2], wurde dieses PEP als zurückgezogen betrachtet.

Zusammenfassung

Dieses PEP schlägt die Zusammenführung des Unladen Swallow-Projekts [3] in den Quellbaum von CPython vor. Unladen Swallow ist ein Open-Source-Zweig von CPython, der sich auf die Leistung konzentriert. Unladen Swallow ist quellcode-kompatibel mit gültigen Python 2.6.4-Anwendungen und C-Erweiterungsmodulen.

Unladen Swallow fügt CPython einen Just-In-Time (JIT)-Compiler hinzu, der die Kompilierung ausgewählter Python-Codes in optimierten Maschinencode ermöglicht. Über klassische statische Compiler-Optimierungen hinaus nutzt der JIT-Compiler von Unladen Swallow Laufzeitdaten, um überprüfte Annahmen über das Codeverhalten zu treffen, was die Erzeugung schnelleren Maschinencodes ermöglicht.

Dieses PEP schlägt die Integration von Unladen Swallow in den Entwicklungsbaum von CPython in einem separaten Zweig namens py3k-jit vor, der schließlich mit dem Hauptzweig py3k zusammengeführt werden soll. Obwohl Unladen Swallow keineswegs fertig oder perfekt ist, sind wir der Meinung, dass Unladen Swallow eine ausreichende Reife erreicht hat, um in die Roadmap von CPython aufgenommen zu werden. Wir haben uns bemüht, eine stabile Plattform zu schaffen, auf der das breitere CPython-Entwicklungsteam aufbauen kann, eine Plattform, die über Jahre hinweg steigende Leistung liefern wird.

Dieses PEP beschreibt die Implementierung von Unladen Swallow und wie sie sich von CPython 2.6.4 unterscheidet; die zur Leistungsmessung verwendeten Benchmarks; die zur Sicherstellung von Korrektheit und Kompatibilität verwendeten Werkzeuge; die Auswirkungen auf die aktuelle Plattformunterstützung von CPython; und die Auswirkungen auf den Kernentwicklungsprozess von CPython. Das PEP schließt mit einem vorgeschlagenen Zusammenführungsplan und kurzen Hinweisen auf mögliche Richtungen für zukünftige Arbeiten.

Wir bitten den BDFL um Folgendes:

  • Genehmigung des Gesamtkonzepts zur Hinzufügung eines Just-In-Time-Compilers zu CPython, basierend auf dem unten dargelegten Design.
  • Erlaubnis, die Arbeit am Just-In-Time-Compiler im CPython-Quellbaum fortzusetzen.
  • Erlaubnis, den Just-In-Time-Compiler schließlich in den py3k-Zweig zu überführen, sobald alle blockierenden Probleme [31] behoben sind.
  • Ein Pony.

Begründung, Implementierung

Viele Unternehmen und Einzelpersonen wünschen sich, dass Python schneller wird, um es für mehr Projekte einsetzen zu können. Google ist ein solches Unternehmen.

Unladen Swallow ist ein von Google gesponserter Zweig von CPython, der initiiert wurde, um die Leistung zahlreicher Python-Bibliotheken, -Tools und -Anwendungen von Google zu verbessern. Um die Übernahme von Unladen Swallow so einfach wie möglich zu gestalten, verfolgte das Projekt zunächst vier Ziele:

  • Eine Leistungsverbesserung von 5x gegenüber dem Basiswert von CPython 2.6.4 für Single-Threaded-Code.
  • 100% Quellcode-Kompatibilität mit gültigen CPython 2.6-Anwendungen.
  • 100% Quellcode-Kompatibilität mit gültigen CPython 2.6 C-Erweiterungsmodulen.
  • Design für die spätere Rücküberführung in CPython.

Wir haben 2.6.4 als Basis gewählt, da Google intern CPython 2.4 verwendet und ein direkter Sprung von CPython 2.4 zu CPython 3.x als nicht durchführbar erachtet wurde.

Um die gewünschte Leistung zu erzielen, hat Unladen Swallow einen Just-In-Time (JIT)-Compiler [51] in der Tradition von Urs Hoelzles Arbeit an Self [52] implementiert, der Laufzeitdaten sammelt und diese für Compile-Zeit-Optimierungen nutzt. Dies ähnelt dem Ansatz der aktuellen Generation von JavaScript-Engines [59], [60]; den meisten Java Virtual Machines [63]; Rubinius [61], MacRuby [62] und anderen Ruby-Implementierungen; Psyco [64]; und anderen.

Wir lehnen ausdrücklich jede Annahme ab, dass unsere Ideen originell sind. Wir haben versucht, veröffentlichte Arbeiten anderer Forscher wiederzuverwenden, wo immer dies möglich war. Wenn wir originelle Arbeit geleistet haben, dann durch Zufall. Wir haben uns bemüht, so weit wie möglich gute Ideen aus allen Ecken der akademischen und industriellen Gemeinschaft zu übernehmen. Eine Teilliste der Forschungsarbeiten, die Unladen Swallow beeinflusst haben, ist im Unladen Swallow Wiki verfügbar [54].

Die wichtigste Erkenntnis bei der Optimierung dynamischer Sprachen ist, dass sie nur theoretisch dynamisch sind; in der Praxis ist jede einzelne Funktion oder Codeausschnitt relativ statisch und verwendet einen stabilen Satz von Typen und Kindfunktionen. Der aktuelle CPython-Bytecode-Interpreter geht vom schlimmsten Fall des Codes aus, den er ausführt, nämlich dass der Benutzer jederzeit die Funktion len() überschreiben oder einen noch nie zuvor gesehenen Typ an eine Funktion übergeben könnte. In der Praxis geschieht dies nie, aber der Benutzercode zahlt für diese Unterstützung. Unladen Swallow nutzt die relativ statische Natur des Benutzercodes, um die Leistung zu verbessern.

Auf hoher Ebene funktioniert der Unladen Swallow JIT-Compiler, indem er den CPython-Bytecode in plattformspezifischen Maschinencode übersetzt. Dabei werden Laufzeitdaten und klassische Compiler-Optimierungen verwendet, um die Qualität des generierten Maschinencodes zu verbessern. Da wir nur Ressourcen für die Kompilierung von Python-Code aufwenden wollen, der tatsächlich zur Laufzeit des Programms beiträgt, wird eine Online-Heuristik verwendet, um zu beurteilen, wie "heiß" eine gegebene Funktion ist. Sobald der "Hotness"-Wert einer Funktion einen bestimmten Schwellenwert überschreitet, wird sie zur Kompilierung und Optimierung ausgewählt. Bis eine Funktion als "heiß" eingestuft wird, läuft sie jedoch in der standardmäßigen CPython-Eval-Schleife, die in Unladen Swallow instrumentiert wurde, um interessante Daten über jeden ausgeführten Bytecode aufzuzeichnen. Diese Laufzeitdaten werden verwendet, um die Flexibilität des generierten Maschinencodes zu reduzieren und uns zu ermöglichen, den häufigsten Fall zu optimieren. Zum Beispiel sammeln wir Daten darüber:

  • Ob eine Verzweigung genommen/nicht genommen wurde. Wenn eine Verzweigung nie genommen wird, werden wir sie nicht in Maschinencode kompilieren.
  • Von Operatoren verwendete Typen. Wenn wir feststellen, dass a + b nur zur Addition von ganzen Zahlen verwendet wird, unterstützt der für diesen Ausschnitt generierte Maschinencode keine Addition von Gleitkommazahlen.
  • An jedem Aufrufpunkt aufgerufene Funktionen. Wenn wir feststellen, dass ein bestimmter foo()-Aufruf immer dieselbe foo-Funktion aufruft, können wir den Aufruf optimieren oder weginlinen.

Eine vollständige Liste der gesammelten Datenpunkte und ihrer Verwendung finden Sie unter [55].

Wenn jedoch zufällig die historisch nicht genommene Verzweigung nun genommen wird oder ein für ganze Zahlen optimierter a + b-Ausschnitt zwei Zeichenketten erhält, müssen wir dies unterstützen. Wir dürfen die Semantik von Python nicht ändern. Jeder dieser optimierten Maschinencodeabschnitte wird von einem Guard vorangestellt, der prüft, ob die bei der Optimierung getroffenen vereinfachenden Annahmen noch gelten. Wenn die Annahmen weiterhin gültig sind, führen wir den optimierten Maschinencode aus; wenn nicht, kehren wir zum Interpreter zurück und machen dort weiter, wo wir aufgehört haben.

Wir haben uns entschieden, eine Reihe bestehender Compiler-Bibliotheken namens LLVM [4] für die Codegenerierung und Codeoptimierung wiederzuverwenden. Dies hat unserem kleinen Team erspart, sich mit der Codegenerierung auf mehreren Maschinenbefehlssätzen auseinandersetzen und diese debuggen zu müssen, sowie eine große Anzahl klassischer Compiler-Optimierungen implementieren zu müssen. Das Projekt wäre ohne eine solche Wiederverwendung von Code nicht möglich gewesen. Wir haben festgestellt, dass LLVM einfach zu modifizieren ist und seine Community empfänglich für unsere Vorschläge und Änderungen ist.

Etwas detaillierter: Der JIT von Unladen Swallow kompiliert CPython-Bytecode in LLVMs eigene Zwischenrepräsentation (IR) [94] und berücksichtigt dabei Laufzeitdaten aus der CPython-Eval-Schleife. Anschließend führen wir eine Reihe von LLVMs integrierten Optimierungsdurchläufen durch, die eine kleinere, optimierte Version des ursprünglichen LLVM IR erzeugen. LLVM wandelt dann das IR in plattformspezifischen Maschinencode um, wobei Registerallokation, Instruktionsplanung und notwendige Relokationen durchgeführt werden. Diese Anordnung der Kompilierungspipeline ermöglicht es, den LLVM-basierten JIT bei der Kompilierung eines python-Binärprogramms einfach wegzulassen, indem --without-llvm an ./configure übergeben wird. Verschiedene Anwendungsfälle für dieses Flag werden später diskutiert.

Eine vollständige Beschreibung, wie Unladen Swallow funktioniert, finden Sie in der Dokumentation von Unladen Swallow [53], [55].

Unladen Swallow hat sich auf die Verbesserung der Leistung von Single-Threaded-Pure-Python-Code konzentriert. Wir haben uns nicht bemüht, die globale Interpreter-Sperre (GIL) von CPython zu entfernen. Wir sind der Meinung, dass dies von unserer Arbeit getrennt ist und aufgrund seiner Sensibilität am besten in einem Hauptentwicklungszweig durchgeführt wird. Wir haben erwogen, die GIL-Entfernung zu einem Teil von Unladen Swallow zu machen, waren aber besorgt über die Möglichkeit, subtile Fehler einzuführen, wenn unsere Arbeit von CPython 2.6 auf 3.x portiert wird.

Ein JIT-Compiler ist ein äußerst vielseitiges Werkzeug, und wir haben sein volles Potenzial bei weitem nicht ausgeschöpft. Wir haben versucht, ein ausreichend flexibles Framework zu schaffen, auf dem die breitere CPython-Entwicklungsgemeinschaft über Jahre hinweg aufbauen und in jeder nachfolgenden Version eine höhere Leistung erzielen kann.

Alternativen

Es gibt eine Reihe alternativer Strategien zur Verbesserung der Python-Leistung, die wir in Betracht gezogen haben, aber für unbefriedigend befanden.

  • Cython, Shedskin: Cython [101] und Shedskin [102] sind beides statische Compiler für Python. Wir betrachten diese als nützliche, aber begrenzte Workarounds für die historisch schlechte Leistung von CPython. Shedskin unterstützt nicht die vollständige Python-Standardbibliothek [103], während Cython für optimale Leistung manuelle Cython-spezifische Annotationen erfordert.

    Statische Compiler wie diese sind nützlich für das Schreiben von Erweiterungsmodulen, ohne sich um die Referenzzählung kümmern zu müssen, aber da sie statische Ahead-of-Time-Compiler sind, können sie nicht den vollen Bereich des Codes optimieren, der von einem Just-In-Time-Compiler, der durch Laufzeitdaten informiert wird, berücksichtigt wird.

  • IronPython: IronPython [106] ist Python auf Microsofts .Net-Plattform. Es wird nicht aktiv auf Mono getestet [107], was bedeutet, dass es im Wesentlichen nur unter Windows funktioniert und daher als allgemeiner CPython-Ersatz ungeeignet ist.
  • Jython: Jython [108] ist eine vollständige Implementierung von Python 2.5, aber deutlich langsamer als Unladen Swallow (3-5x bei gemessenen Benchmarks) und unterstützt keine CPython-Erweiterungsmodule [109], was die Migration großer Anwendungen unerschwinglich machen würde.
  • Psyco: Psyco [64] ist ein spezialisierter JIT-Compiler für CPython, implementiert als Erweiterungsmodul. Es verbessert hauptsächlich die Leistung für numerischen Code. Vorteile: existiert; macht einige Codes schneller. Nachteile: nur 32-Bit, keine Pläne für 64-Bit-Unterstützung; unterstützt nur x86; sehr schwer zu warten; inkompatibel mit SSE2-optimiertem Code aufgrund von Ausrichtungsproblemen.
  • PyPy: PyPy [65] erzielt gute Leistung bei numerischem Code, ist aber bei einigen Workloads langsamer als Unladen Swallow. Die Migration großer Anwendungen von CPython zu PyPy wäre unerschwinglich: PyPys JIT-Compiler unterstützt nur die Generierung von 32-Bit-x86-Code; wichtige Module wie MySQLdb und pycrypto lassen sich gegen PyPy nicht kompilieren; PyPy bietet keine Einbettungs-API, geschweige denn dieselbe API wie CPython.
  • PyV8: PyV8 [110] ist ein experimenteller Python-zu-JavaScript-Compiler im Alpha-Stadium, der auf V8 läuft. PyV8 implementiert nicht die gesamte Python-Sprache und unterstützt keine CPython-Erweiterungsmodule.
  • WPython: WPython [104] ist eine wortbasierte Neuimplementierung der Interpreter-Schleife von CPython. Während es eine moderate Verbesserung der Interpreter-Leistung bietet [105], ist es kein Entweder-Oder-Ersatz für einen Just-In-Time-Compiler. Ein Interpreter wird nie so schnell sein wie optimierter Maschinencode. Wir betrachten WPython und ähnliche Interpreter-Verbesserungen als ergänzend zu unserer Arbeit und nicht als Konkurrenten.

Performance

Benchmarks

Unladen Swallow hat eine ziemlich große Suite von Benchmarks entwickelt, die von synthetischen Mikrobenchmarks, die eine einzelne Funktion testen sollen, bis hin zu Ganzanwendungs-Makrobenchmarks reichen. Die Inspiration für diese Benchmarks kam teilweise von Drittanbietern (im Fall des html5lib-Benchmarks), von Googles eigenen internen Workloads (slowspitfire, pickle, unpickle) sowie von Werkzeugen und Bibliotheken, die in der breiteren Python-Community intensiv genutzt werden (django, 2to3, spambayes). Diese Benchmarks werden über eine einzige Schnittstelle namens perf.py ausgeführt, die sich um die Erfassung von Speicherverbrauchsinformationen, die grafische Darstellung der Leistung und die Durchführung von Statistiken zu den Benchmark-Ergebnissen zur Sicherstellung der Signifikanz kümmert.

Die vollständige Liste der verfügbaren Benchmarks finden Sie im Unladen Swallow Wiki [43], einschließlich Anweisungen zum Herunterladen und Ausführen der Benchmarks für sich selbst. Alle unsere Benchmarks sind Open-Source; keine sind proprietär für Google. Wir glauben, dass diese Benchmark-Sammlung als nützliches Werkzeug zur Benchmark-Erstellung für jede vollständige Python-Implementierung dient, und tatsächlich nutzt PyPy diese Benchmarks bereits für seine eigenen Leistungstests [80], [95]. Wir begrüßen dies und suchen zusätzliche Workloads für die Benchmark-Suite von der Python-Community.

Wir haben unsere Bemühungen auf die Erfassung von Makro-Benchmarks und Benchmarks konzentriert, die reale Anwendungen so gut wie möglich simulieren, wenn die Ausführung einer ganzen Anwendung nicht praktikabel ist. Auf einer anderen Achse konzentrierte sich unsere Benchmark-Sammlung ursprünglich auf die Art von Workloads, die Googles Python-Code sieht (Webanwendungen, Textverarbeitung), obwohl wir die Sammlung inzwischen erweitert haben, um Workloads einzuschließen, die Google überhaupt nicht betreffen. Wir haben bisher stark numerische Workloads gemieden, da NumPy [79] bereits hervorragende Arbeit bei solchem Code leistet und die Verbesserung der numerischen Leistung daher keine anfängliche Priorität für das Team war; wir haben begonnen, solche Benchmarks in die Sammlung aufzunehmen [96] und mit der Optimierung von numerischem Python-Code begonnen.

Über diese Benchmarks hinaus gibt es auch eine Reihe von Workloads, die wir explizit nicht benchmarken wollen. Unladen Swallow konzentriert sich auf die Verbesserung der Leistung von Pure-Python-Code, daher ist die Leistung von Erweiterungsmodulen wie NumPy uninteressant, da die Kernroutinen von NumPy in C implementiert sind. Ebenso würden Workloads, die viel I/O beinhalten, wie GUIs, Datenbanken oder socket-intensive Anwendungen, unserer Meinung nach keine genaue Messung von Interpreter- oder Codegenerierungsoptimierungen ermöglichen. Dennoch gibt es sicherlich Raum zur Verbesserung der Leistung von C-Sprach-Erweiterungsmodulen in der Standardbibliothek, und als solche haben wir Benchmarks für die Module cPickle und re hinzugefügt.

Leistung im Vergleich zu CPython 2.6.4

Die folgenden Diagramme vergleichen das arithmetische Mittel mehrerer Benchmark-Iterationen für CPython 2.6.4 und Unladen Swallow. perf.py sammelt mehr Daten als diese, und tatsächlich ist das arithmetische Mittel nicht alles; wir reproduzieren nur den Mittelwert aus Gründen der Kürze. Wir fügen den t-Wert aus dem zweiseitigen T-Test von Student [44] mit einem Konfidenzintervall von 95 % ein, um die Signifikanz des Ergebnisses anzuzeigen. Die meisten Benchmarks werden 100 Mal ausgeführt, obwohl einige länger laufende Ganzanwendungs-Benchmarks weniger oft ausgeführt werden.

Eine Beschreibung jedes dieser Benchmarks ist im Unladen Swallow Wiki verfügbar [43].

Befehl

./perf.py -r -b default,apps ../a/python ../b/python

32-Bit; gcc 4.0.3; Ubuntu Dapper; Intel Core2 Duo 6600 @ 2.4GHz; 2 Kerne; 4MB L2-Cache; 4 GB RAM

Benchmark CPython 2.6.4 Unladen Swallow r988 Änderung Signifikanz Zeitplan
2to3 25,13 s 24,87 s 1,01x schneller t=8,94 http://tinyurl.com/yamhrpg
django 1,08 s 0,80 s 1,35x schneller t=315,59 http://tinyurl.com/y9mrn8s
html5lib 14,29 s 13,20 s 1,08x schneller t=2,17 http://tinyurl.com/y8tyslu
nbody 0,51 s 0,28 s 1,84x schneller t=78,007 http://tinyurl.com/y989qhg
rietveld 0,75 s 0,55 s 1,37x schneller Unsignifikant http://tinyurl.com/ye7mqd3
slowpickle 0,75 s 0,55 s 1,37x schneller t=20,78 http://tinyurl.com/ybrsfnd
slowspitfire 0,83 s 0,61 s 1,36x schneller t=2124,66 http://tinyurl.com/yfknhaw
slowunpickle 0,33 s 0,26 s 1,26x schneller t=15,12 http://tinyurl.com/yzlakoo
spambayes 0,31 s 0,34 s 1,10x langsamer Unsignifikant http://tinyurl.com/yem62ub

64-Bit; gcc 4.2.4; Ubuntu Hardy; AMD Opteron 8214 HE @ 2,2 GHz; 4 Kerne; 1 MB L2-Cache; 8 GB RAM

Benchmark CPython 2.6.4 Unladen Swallow r988 Änderung Signifikanz Zeitplan
2to3 31,98 s 30,41 s 1,05x schneller t=8,35 http://tinyurl.com/ybcrl3b
django 1,22 s 0,94 s 1,30x schneller t=106,68 http://tinyurl.com/ybwqll6
html5lib 18,97 s 17,79 s 1,06x schneller t=2,78 http://tinyurl.com/yzlyqvk
nbody 0,77 s 0,27 s 2,86x schneller t=133,49 http://tinyurl.com/yeyqhbg
rietveld 0,74 s 0,80 s 1,08x langsamer t=-2,45 http://tinyurl.com/yzjc6ff
slowpickle 0,91 s 0,62 s 1,48x schneller t=28,04 http://tinyurl.com/yf7en6k
slowspitfire 1,01 s 0,72 s 1,40x schneller t=98,70 http://tinyurl.com/yc8pe2o
slowunpickle 0,51 s 0,34 s 1,51x schneller t=32,65 http://tinyurl.com/yjufu4j
spambayes 0,43 s 0,45 s 1,06x langsamer Unsignifikant http://tinyurl.com/yztbjfp

Viele dieser Benchmarks zeigen unter Unladen Swallow Leistungseinbußen, da die aktuelle Version die Ausführung blockiert, um Python-Funktionen in Maschinencode zu kompilieren. Dies führt zu dem Verhalten, das in den Zeitliniendiagrammen für die Benchmarks html5lib und rietveld zu sehen ist, und verlangsamt die Gesamtleistung von 2to3. Wir haben einen aktiven Entwicklungszweig, um dieses Problem zu beheben ([46], [47]), aber die Arbeit innerhalb der Beschränkungen des aktuellen Threading-Systems von CPython hat den Prozess kompliziert und weit mehr Sorgfalt und Zeit erfordert als ursprünglich erwartet. Wir betrachten dieses Problem als kritisch für die endgültige Zusammenführung in den Zweig py3k.

Wir haben unser ursprüngliches Ziel einer Leistungsverbesserung um das 5-fache offensichtlich nicht erreicht. Eine Leistungs-Retrospektive folgt, die erklärt, warum wir unser ursprüngliches Leistungsziel nicht erreicht haben. Wir pflegen eine Liste noch zu implementierender Leistungsarbeiten [50].

Speicherverbrauch

Die folgende Tabelle zeigt den maximalen Speicherverbrauch (in Kilobytes) für jeden der Standard-Benchmarks von Unladen Swallow für CPython 2.6.4 und Unladen Swallow r988, sowie eine Zeitlinie des Speicherverbrauchs über die Lebensdauer des Benchmarks. Wir fügen Tabellen für 32- und 64-Bit-Binärprogramme hinzu. Der Speicherverbrauch wurde auf Linux 2.6-Systemen gemessen, indem die Abschnitte Private_ aus den /proc/$pid/smaps Pseudo-Dateien des Kernels summiert wurden [45].

Befehl

./perf.py -r --track_memory -b default,apps ../a/python ../b/python

32-Bit

Benchmark CPython 2.6.4 Unladen Swallow r988 Änderung Zeitplan
2to3 26396 KB 46896 KB 1,77x http://tinyurl.com/yhr2h4z
django 10028 KB 27740 KB 2,76x http://tinyurl.com/yhan8vs
html5lib 150028 KB 173924 KB 1,15x http://tinyurl.com/ybt44en
nbody 3020 KB 16036 KB 5,31x http://tinyurl.com/ya8hltw
rietveld 15008 KB 46400 KB 3,09x http://tinyurl.com/yhd5dra
slowpickle 4608 KB 16656 KB 3,61x http://tinyurl.com/ybukyvo
slowspitfire 85776 KB 97620 KB 1,13x http://tinyurl.com/y9vj35z
slowunpickle 3448 KB 13744 KB 3,98x http://tinyurl.com/yexh4d5
spambayes 7352 KB 46480 KB 6,32x http://tinyurl.com/yem62ub

64-Bit

Benchmark CPython 2.6.4 Unladen Swallow r988 Änderung Zeitplan
2to3 51596 KB 82340 KB 1,59x http://tinyurl.com/yljg6rs
django 16020 KB 38908 KB 2,43x http://tinyurl.com/ylqsebh
html5lib 259232 KB 324968 KB 1,25x http://tinyurl.com/yha6oee
nbody 4296 KB 23012 KB 5,35x http://tinyurl.com/yztozza
rietveld 24140 KB 73960 KB 3,06x http://tinyurl.com/ybg2nq7
slowpickle 4928 KB 23300 KB 4,73x http://tinyurl.com/yk5tpbr
slowspitfire 133276 KB 148676 KB 1,11x http://tinyurl.com/y8bz2xe
slowunpickle 4896 KB 16948 KB 3,46x http://tinyurl.com/ygywwoc
spambayes 10728 KB 84992 KB 7,92x http://tinyurl.com/yhjban5

Der erhöhte Speicherverbrauch resultiert aus a) LLVM-Bibliotheken für Codegenerierung, Analyse und Optimierung; b) nativem Code; c) Speicherverbrauchsproblemen oder -lecks in LLVM; d) Datenstrukturen, die zur Optimierung und Generierung von Maschinencode benötigt werden; e) noch nicht kategorisierten anderen Quellen.

Obwohl wir erhebliche Fortschritte bei der Reduzierung des Speicherverbrauchs seit der ersten naiven JIT-Implementierung gemacht haben [42], gibt es offensichtlich noch mehr zu tun. Wir glauben, dass noch Speicher eingespart werden kann, ohne die Leistung zu beeinträchtigen. Wir haben uns eher auf die reine Leistung konzentriert und noch keinen konzertierten Vorstoß zur Reduzierung des Speicherverbrauchs unternommen. Wir betrachten die Reduzierung des Speicherverbrauchs als blockierendes Problem für die endgültige Zusammenführung in den Zweig py3k. Wir bitten die Community um Hinweise zu einem akzeptablen erhöhten Speicherverbrauch.

Startzeit

Die statische Verlinkung der LLVM-Bibliotheken für Codegenerierung, Analyse und Optimierung verlängert die Startzeit des Python-Binärprogramms. C++-statische Initialisierer, die von LLVM verwendet werden, erhöhen ebenfalls die Startzeit, ebenso wie der Import der Sammlung von vorcompilierten C-Laufzeitroutinen, die wir in Python-Code einfügen wollen.

Ergebnisse der startup-Benchmarks von Unladen Swallow

$ ./perf.py -r -b startup /tmp/cpy-26/bin/python /tmp/unladen/bin/python

### normal_startup ###
Min: 0.219186 -> 0.352075: 1.6063x slower
Avg: 0.227228 -> 0.364384: 1.6036x slower
Significant (t=-51.879098, a=0.95)
Stddev: 0.00762 -> 0.02532: 3.3227x larger
Timeline: http://tinyurl.com/yfe8z3r

### startup_nosite ###
Min: 0.105949 -> 0.264912: 2.5004x slower
Avg: 0.107574 -> 0.267505: 2.4867x slower
Significant (t=-703.557403, a=0.95)
Stddev: 0.00214 -> 0.00240: 1.1209x larger
Timeline: http://tinyurl.com/yajn8fa

### bzr_startup ###
Min: 0.067990 -> 0.097985: 1.4412x slower
Avg: 0.084322 -> 0.111348: 1.3205x slower
Significant (t=-37.432534, a=0.95)
Stddev: 0.00793 -> 0.00643: 1.2330x smaller
Timeline: http://tinyurl.com/ybdm537

### hg_startup ###
Min: 0.016997 -> 0.024997: 1.4707x slower
Avg: 0.026990 -> 0.036772: 1.3625x slower
Significant (t=-53.104502, a=0.95)
Stddev: 0.00406 -> 0.00417: 1.0273x larger
Timeline: http://tinyurl.com/ycout8m

bzr_startup und hg_startup messen, wie lange es dauert, bis Bazaar bzw. Mercurial ihre Hilfebildschirme anzeigen. startup_nosite führt python -S viele Male aus; die Verwendung der Option -S ist selten, aber wir glauben, dass dies einen guten Hinweis darauf gibt, woher die erhöhte Startzeit stammt.

Unladen Swallow hat Fortschritte bei der Optimierung der Startzeit gemacht, aber es gibt noch mehr zu tun und weitere Optimierungen zu implementieren. Die Verbesserung der Startzeit ist ein hochpriorisiertes Element [33] in der Zusammenführungs-Punchlist von Unladen Swallow.

Binäre Größe

Die statische Verlinkung der LLVM-Bibliotheken für Codegenerierung, Analyse und Optimierung erhöht signifikant die Größe des python-Binärprogramms. Die folgenden Tabellen zeigen stripped On-Disk-Binärgrößen; die Binärprogramme sind stripped, um besser mit den Konfigurationen übereinzustimmen, die von Systempaketmanagern verwendet werden. Wir glauben, dass dies die realistischste Messung einer Änderung der Binärgröße ist.

Binäre Größe CPython 2.6.4 CPython 3.1.1 Unladen Swallow r1041
32-Bit 1,3 M 1,4 M 12 M
64-Bit 1,6 M 1,6 M 12 M

Die erhöhte Binärgröße wird durch die statische Verlinkung der LLVM-Bibliotheken für Codegenerierung, Analyse und Optimierung in das python-Binärprogramm verursacht. Dies kann einfach dadurch behoben werden, dass LLVM so modifiziert wird, dass es die dynamische Verlinkung besser unterstützt und diese dann verwendet wird, anstelle der aktuellen statischen Verlinkung. Vorerst bietet die statische Verlinkung jedoch einen genauen Einblick in die Kosten der Verlinkung gegen LLVM.

Selbst bei statischer Verlinkung glauben wir, dass es noch Spielraum zur Verbesserung der On-Disk-Binärgröße gibt, indem die Abhängigkeiten von Unladen Swallow von LLVM reduziert werden. Dieses Problem wird aktiv angegangen [32].

Leistungs-Retrospektive

Unser ursprüngliches Ziel für Unladen Swallow war eine Leistungsverbesserung um das 5-fache gegenüber CPython 2.6. Wir haben dieses Ziel nicht erreicht, geschweige denn, um es unverblümt zu sagen, auch nur annähernd. Warum hat das Projekt dieses Ziel nicht erreicht, und kann ein LLVM-basierter JIT jemals dieses Ziel erreichen?

Warum hat Unladen Swallow sein 5x-Ziel nicht erreicht? Der Hauptgrund war, dass LLVM mehr Arbeit erforderte, als wir ursprünglich erwartet hatten. Basierend auf der Tatsache, dass Apple Produkte auf LLVM-Basis vertrieb [81] und andere Hochsprachen erfolgreich LLVM-basierte JITs implementiert hatten ([61], [62], [82]), hatten wir angenommen, dass LLVMs JIT relativ frei von Showstopper-Bugs ist.

Das hat sich als falsch erwiesen. Wir mussten unsere Aufmerksamkeit von der Leistung abwenden, um eine Reihe kritischer Fehler in der JIT-Infrastruktur von LLVM zu beheben (z.B. [83], [84]) sowie eine Reihe von Nice-to-have-Verbesserungen, die weitere Optimierungen in verschiedenen Bereichen ermöglichen würden (z.B. [86], [85], [87]). LLVMs statische Codegenerierungseinrichtungen, Werkzeuge und Optimierungsdurchläufe sind stabil und intensiv getestet, aber die Just-In-Time-Infrastruktur war relativ unerprobt und fehlerhaft. Wir haben dies behoben.

(Unsere Hypothese ist, dass wir auf diese Probleme gestoßen sind – Probleme, die andere Projekte vermieden hatten –, weil die Testsuite der Standardbibliothek von CPython komplex und gründlich ist.)

Wir haben auch Ingenieursressourcen von der Leistung auf Support-Tools wie gdb und oProfile umgelenkt. gdb funktionierte überhaupt nicht gut mit JIT-Compilern, und LLVM hatte zuvor keine Integration mit oProfile. JIT-fähige Debugger und Profiler waren für das Projekt sehr wertvoll, und wir bereuen es nicht, unsere Zeit in diese Richtungen gelenkt zu haben. Weitere Informationen finden Sie in den Abschnitten Debugging und Profiling.

Kann ein LLVM-basierter CPython JIT jemals das 5-fache Leistungsziel erreichen? Die Benchmark-Ergebnisse für JIT-basierte JavaScript-Implementierungen deuten darauf hin, dass 5x tatsächlich möglich ist, ebenso wie die Ergebnisse, die PyPys JIT für numerische Workloads geliefert hat. Die Erfahrung von Self-92 [52] ist ebenfalls lehrreich.

Kann LLVM das liefern? Wir glauben, dass wir gerade erst an der Oberfläche dessen kratzen, was unser LLVM-basierter JIT liefern kann. Die Optimierungen, die wir bisher in dieses System integriert haben, haben erhebliche Früchte getragen (z. B. [88], [89], [90]). Unsere bisherigen Erfahrungen zeigen, dass der limitierende Faktor für die Leistung von Unladen Swallow die Ingenieurszyklen sind, die benötigt werden, um die Literatur zu implementieren. Wir haben festgestellt, dass LLVM einfach zu handhaben und zu modifizieren ist, und seine integrierten Optimierungen haben die Aufgabe der Implementierung von Optimierungen auf Python-Ebene erheblich vereinfacht.

Ein Überblick über weitere Leistungsmöglichkeiten wird im Abschnitt Future Work diskutiert.

Korrektheit und Kompatibilität

Die Suite für Korrektheitstests von Unladen Swallow umfasst die Testsuite von CPython (unter Lib/test/) sowie eine Reihe wichtiger Drittanbieter-Anwendungen und -Bibliotheken [6]. Eine vollständige Liste dieser Anwendungen und Bibliotheken wird unten wiedergegeben. Alle Abhängigkeiten, die diese Pakete benötigen, wie z. B. zope.interface [34], werden ebenfalls indirekt als Teil des Testens des primären Pakets getestet, wodurch der Korpus des getesteten Drittanbieter-Python-Codes erweitert wird.

  • 2to3
  • Cheetah
  • cvs2svn
  • Django
  • Nose
  • NumPy
  • PyCrypto
  • pyOpenSSL
  • PyXML
  • Setuptools
  • SQLAlchemy
  • SWIG
  • SymPy
  • Twisted
  • ZODB

Diese Anwendungen bestehen alle relevanten Tests, wenn sie unter Unladen Swallow ausgeführt werden. Beachten Sie, dass einige Tests, die gegen unsere Basislinie von CPython 2.6.4 fehlschlugen, deaktiviert wurden, ebenso wie Tests, die Annahmen über CPython-Interna wie exakte Bytecode-Nummern oder Bytecode-Formate machten. Jedes Paket mit deaktivierten Tests enthält eine Datei README.unladen, die die Änderungen detailliert beschreibt (z. B. [37]).

Darüber hinaus wird Unladen Swallow automatisch gegen eine Reihe interner Google Python-Bibliotheken und -Anwendungen getestet. Dazu gehören Googles interne Python-Bindings für BigTable [35], die Mondrian Code-Review-Anwendung [36] und Googles Python-Standardbibliothek, unter anderem. Die erforderlichen Änderungen, um diese Projekte unter Unladen Swallow auszuführen, lassen sich durchweg in eines von drei Lager aufteilen

  • Hinzufügen der Kompatibilität mit der CPython 2.6 C API. Da Google intern immer noch hauptsächlich CPython 2.4 verwendet, mussten wir Verwendungen von int auf Py_ssize_t und ähnliche API-Änderungen umwandeln.
  • Behebung oder Deaktivierung expliziter, fehlerhafter Tests der CPython-Versionsnummer.
  • Bedingte Deaktivierung von Code, der auf Bugs in CPython 2.4 reagierte oder von ihnen abhing, die inzwischen behoben wurden.

Das Testen gegen diese breite Palette von öffentlichen und proprietären Anwendungen und Bibliotheken war entscheidend für die Sicherstellung der Korrektheit von Unladen Swallow. Tests haben Bugs aufgedeckt, die wir ordnungsgemäß korrigiert haben. Unser automatisiertes Regressions-Testsystem hat uns hohes Vertrauen in unsere Änderungen gegeben, während wir voranschritten.

Zusätzlich zu den Drittanbieter-Tests haben wir die Testsuite von CPython um weitere Tests für Eckfälle der Sprache oder Implementierung erweitert, die unserer Meinung nach nicht getestet oder unterdefiniert waren (z. B. [48], [49]). Diese waren besonders wichtig bei der Implementierung von Optimierungen und halfen sicherzustellen, dass wir die dunkleren Ecken von Python nicht versehentlich beschädigt haben.

Wir haben auch eine Testsuite erstellt, die sich ausschließlich auf den LLVM-basierten JIT-Compiler und die dafür implementierten Optimierungen konzentriert [38]. Aufgrund der Komplexität und Feinheit, die beim Schreiben eines optimierenden Compilers inhärent sind, haben wir versucht, die zu kompilierenden und optimierenden Konstrukte, Szenarien und Eckfälle erschöpfend aufzulisten. Die JIT-Tests umfassen auch Tests für Dinge wie das JIT-Hotness-Modell, was es zukünftigen CPython-Entwicklern erleichtert, sie zu warten und zu verbessern.

Wir haben kürzlich mit Fuzz-Tests begonnen [39], um den Compiler zu belasten. Wir haben in der Vergangenheit sowohl pyfuzz [40] als auch Fusil [41] verwendet und empfehlen, sie als automatischen Teil des CPython-Testprozesses einzuführen.

Bekannte Inkompatibilitäten

Die einzige Anwendung oder Bibliothek, von der wir wissen, dass sie nicht mit Unladen Swallow funktioniert, aber mit CPython 2.6.4 funktioniert, ist Psyco [64]. Wir sind uns einiger Bibliotheken wie PyGame [78] bewusst, die gut mit CPython 2.6.4 funktionieren, aber aufgrund von Änderungen in Unladen Swallow einige Leistungseinbußen erleiden. Wir verfolgen dieses Problem [47] und arbeiten daran, diese Leistungseinbußen zu beheben.

Während Unladen Swallow quellcodekompatibel mit CPython 2.6.4 ist, ist es nicht binärkompatibel. C-Erweiterungsmodule, die gegen eines kompiliert wurden, müssen neu kompiliert werden, um mit dem anderen zu funktionieren.

Die Zusammenführung von Unladen Swallow sollte minimale Auswirkungen auf langlaufende CPython-Optimierungszweige wie WPython haben. WPython [104] und Unladen Swallow sind weitgehend orthogonal, und es gibt keinen technischen Grund, warum beide nicht in CPython zusammengeführt werden könnten. Die Änderungen, die erforderlich sind, um WPython mit einer JIT-erweiterten Version von CPython kompatibel zu machen, sollten minimal sein [113]. Dasselbe sollte für andere CPython-Optimierungsprojekte gelten (z. B. [114]).

Invasive Forks von CPython wie Stackless Python [115] sind schwieriger zu unterstützen. Da es sehr unwahrscheinlich ist, dass Stackless in CPython zusammengeführt wird [116] und ein erhöhter Wartungsaufwand Teil jedes Forks ist, betrachten wir die Kompatibilität mit Stackless als relativ niedrig priorisiert. JIT-kompilierte Stack-Frames verwenden den C-Stack, sodass Stackless sie genauso behandeln sollte wie Aufrufe über Erweiterungsmodule. Wenn sich dies als nicht akzeptabel erweist, könnte Stackless entweder den JIT-Compiler entfernen oder die JIT-Codegenerierung verbessern, um Heap-basierte Stack-Frames besser zu unterstützen [117], [118].

Plattformunterstützung

Unladen Swallow ist inhärent durch die Plattformunterstützung von LLVM begrenzt, insbesondere durch LLVMs JIT-Kompilierungssystem [7]. LLVMs JIT hat die beste Unterstützung auf x86- und x86-64-Systemen, und dies sind die Plattformen, auf denen Unladen Swallow am meisten getestet wurde. Wir sind zuversichtlich in die Unterstützung von LLVM/Unladen Swallow für x86- und x86-64-Hardware. PPC- und ARM-Unterstützung existiert, ist aber nicht weit verbreitet und kann fehlerhaft sein (z. B. [99], [83], [100]).

Unladen Swallow ist bekannt dafür, auf den folgenden Betriebssystemen zu funktionieren: Linux, Darwin, Windows. Unladen Swallow wurde am meisten unter Linux und Darwin getestet, obwohl es unter Windows immer noch kompiliert und seine Tests besteht.

Um Hardware- und Softwareplattformen zu unterstützen, auf denen LLVMs JIT nicht funktioniert, bietet Unladen Swallow eine Option ./configure --without-llvm. Dieses Flag entfernt jeden Teil von Unladen Swallow, der von LLVM abhängt, und liefert eine Python-Binärdatei, die funktioniert und ihre Tests besteht, aber keine Leistungs Vorteile bietet. Diese Konfiguration wird für Hardware empfohlen, die von LLVM nicht unterstützt wird, oder für Systeme, die mehr Wert auf Speichernutzung als auf Leistung legen.

Auswirkungen auf die CPython-Entwicklung

Experimente mit Änderungen an Python oder CPython-Bytecode

Der JIT-Compiler von Unladen Swallow arbeitet mit CPython-Bytecode und ist daher immun gegen Änderungen der Python-Sprache, die nur den Parser betreffen.

Wir empfehlen, dass Änderungen am CPython-Bytecode-Compiler oder an der Semantik einzelner Bytecodes zuerst in der Interpreter-Schleife prototypisiert und dann in den JIT-Compiler portiert werden, sobald die Semantik klar ist. Um dies zu erleichtern, enthält Unladen Swallow eine Konfigurationsoption --without-llvm, die den JIT-Compiler und die gesamte zugehörige Infrastruktur entfernt. Dies belässt die aktuelle Experimentierlast unverändert, sodass Entwickler in der aktuellen Interpreter-Schleife mit geringem Einstiegsaufwand prototypisieren können.

Unladen Swallow begann mit der Implementierung seines JIT-Compilers durch direkte, naive Übersetzungen von Bytecode-Implementierungen in LLVM API-Aufrufe. Wir fanden diesen Prozess leicht verständlich und empfehlen denselben Ansatz für CPython. Wir fügen hier mehrere Beispieländerungen aus dem Unladen Swallow Repository als Beispiele für diesen Entwicklungsstil hinzu: [26], [27], [28], [29].

Debugging

Das Unladen Swallow-Team hat Änderungen an gdb vorgenommen, um die Verwendung von gdb zum Debuggen von JIT-kompiliertem Python-Code zu erleichtern. Diese Änderungen wurden in gdb 7.0 veröffentlicht [17]. Sie ermöglichen es gdb, JIT-generierte Call-Stack-Frames zu identifizieren und zu entwirren. Dies ermöglicht es gdb, für die CPython-Entwicklung weiterhin wie zuvor zu funktionieren, wenn beispielsweise der list-Typ oder integrierte Funktionen geändert werden.

Beispiel-Backtrace nach unseren Änderungen, wobei baz, bar und foo JIT-kompiliert sind

Program received signal SIGSEGV, Segmentation fault.
0x00002aaaabe7d1a8 in baz ()
(gdb) bt
#0 0x00002aaaabe7d1a8 in baz ()
#1 0x00002aaaabe7d12c in bar ()
#2 0x00002aaaabe7d0aa in foo ()
#3 0x00002aaaabe7d02c in main ()
#4 0x0000000000b870a2 in llvm::JIT::runFunction (this=0x1405b70, F=0x14024e0, ArgValues=...)
at /home/rnk/llvm-gdb/lib/ExecutionEngine/JIT/JIT.cpp:395
#5 0x0000000000baa4c5 in llvm::ExecutionEngine::runFunctionAsMain
(this=0x1405b70, Fn=0x14024e0, argv=..., envp=0x7fffffffe3c0)
at /home/rnk/llvm-gdb/lib/ExecutionEngine/ExecutionEngine.cpp:377
#6 0x00000000007ebd52 in main (argc=2, argv=0x7fffffffe3a8,
envp=0x7fffffffe3c0) at /home/rnk/llvm-gdb/tools/lli/lli.cpp:208

Zuvor hätten die JIT-kompilierten Frames dazu geführt, dass gdb falsch entwirrt wurde, was viele offensichtlich falsche Stack-Frames im Stil von #6 0x00002aaaabe7d0aa in ?? () erzeugt hätte.

Höhepunkte

  • gdb 7.0 kann JIT-kompilierte Stack-Frames korrekt parsen und ermöglicht die vollständige Nutzung von gdb für nicht JIT-kompilierte Funktionen, d. h. den größten Teil der CPython-Codebasis.
  • Das Disassemblieren innerhalb eines JIT-kompilierten Stack-Frames gibt automatisch die vollständige Liste der Befehle aus, aus denen diese Funktion besteht. Dies ist ein Fortschritt gegenüber dem Zustand von gdb vor unserer Arbeit: Entwickler mussten die Startadresse der Funktion erraten und den Assemblercode manuell disassemblieren.
  • Der flexible zugrunde liegende Mechanismus ermöglicht es CPython, immer mehr Informationen hinzuzufügen und schließlich die Parität mit der C/C++-Unterstützung in gdb für JIT-kompilierten Maschinencode zu erreichen.

Niedrige Punkte

  • gdb kann keine lokalen Variablen anzeigen oder sagen, in welcher Zeile Sie sich gerade innerhalb einer JIT-kompilierten Funktion befinden. Es kann auch nicht durch JIT-kompilierten Code schrittweise durchlaufen, außer ein Befehl nach dem anderen.
  • Noch nicht in Apples gdb oder Microsofts Visual Studio Debuggern integriert.

Das Unladen Swallow-Team arbeitet mit Apple zusammen, um diese Änderungen in zukünftige gdb-Releases zu integrieren.

Profiling

Unladen Swallow integriert sich mit oProfile 0.9.4 und neuer [18] zur Unterstützung von Assembly-Level-Profiling auf Linux-Systemen. Das bedeutet, dass oProfile JIT-kompilierte Funktionen in seinen Berichten korrekt symbolisieren wird.

Beispielbericht, bei dem die mit #u# beginnenden Symbolnamen JIT-kompilierte Python-Funktionen sind

$ opreport -l ./python | less
CPU: Core 2, speed 1600 MHz (estimated)
Counted CPU_CLK_UNHALTED events (Clock cycles when not halted) with a unit mask of 0x00 (Unhalted core cycles) count 100000
samples % image name symbol name
79589 4.2329 python PyString_FromFormatV
62971 3.3491 python PyEval_EvalCodeEx
62713 3.3354 python tupledealloc
57071 3.0353 python _PyEval_CallFunction
50009 2.6597 24532.jo #u#force_unicode
47468 2.5246 python PyUnicodeUCS2_Decode
45829 2.4374 python PyFrame_New
45173 2.4025 python lookdict_string
43082 2.2913 python PyType_IsSubtype
39763 2.1148 24532.jo #u#render5
38145 2.0287 python _PyType_Lookup
37643 2.0020 python PyObject_GC_UnTrack
37105 1.9734 python frame_dealloc
36849 1.9598 python PyEval_EvalFrame
35630 1.8950 24532.jo #u#resolve
33313 1.7717 python PyObject_IsInstance
33208 1.7662 python PyDict_GetItem
33168 1.7640 python PyTuple_New
30458 1.6199 python PyCFunction_NewEx

Diese Unterstützung ist funktionsfähig, aber noch nicht ausgereift. Unladen Swallow pflegt eine Liste von Punkten, die unserer Meinung nach wichtig sind, um unsere oProfile-Integration zu verbessern und sie für Kern-CPython-Entwickler nützlicher zu machen [19].

Höhepunkte

  • Symbolisierung von JIT-Frames funktioniert in oProfile unter Linux.

Niedrige Punkte

  • Bisher wurde keine Arbeit in die Verbesserung der Symbolisierung von JIT-kompilierten Frames für Apples Shark [20] oder Microsofts Visual Studio Profiling-Tools investiert.
  • Für die oProfile-Ausgabe ist noch etwas Polishing gewünscht.

Wir empfehlen die Verwendung von oProfile 0.9.5 (und neuer), um einen inzwischen behobenen Bug auf x86-64-Plattformen in oProfile zu umgehen. oProfile 0.9.4 funktioniert jedoch auch auf 32-Bit-Plattformen.

Angesichts der einfachen Integration von oProfile mit LLVM [21] und Unladen Swallow [22] sollten auch andere Profiling-Tools einfach zu integrieren sein, sofern sie eine ähnliche JIT-Schnittstelle unterstützen [23].

Wir haben den Prozess zur Verwendung von oProfile zum Profilieren von Unladen Swallow dokumentiert [24]. Dieses Dokument wird in den Doc/-Baum von CPython in die Zusammenführung aufgenommen.

Hinzufügen von C++ zu CPython

Um LLVM zu verwenden, hat Unladen Swallow C++ in den Kern-CPython-Baum und den Build-Prozess eingeführt. Dies ist ein unvermeidlicher Teil der Abhängigkeit von LLVM; obwohl LLVM eine C-API anbietet [8], ist diese begrenzt und macht die von CPython benötigte Funktionalität nicht zugänglich. Aus diesem Grund haben wir die internen Details des Unladen Swallow JIT und seine unterstützende Infrastruktur in C++ implementiert. Wir schlagen nicht vor, den gesamten CPython-Code in C++ zu konvertieren.

Höhepunkte

  • Einfache Nutzung von LLVMs vollständiger, leistungsfähiger Codegenerierung und zugehörigen APIs.
  • Bequeme, abstrakte Datenstrukturen vereinfachen den Code.
  • C++ ist auf relativ kleine Ecken der CPython-Codebasis beschränkt.
  • C++ kann über ./configure --without-llvm deaktiviert werden, was sogar die Abhängigkeit von libstdc++ auslässt.

Niedrige Punkte

  • Entwickler müssen zwei verwandte Sprachen, C und C++, kennen, um an der gesamten Breite von CPython-Interna arbeiten zu können.
  • Ein C++-Styleguide muss entwickelt und durchgesetzt werden. PEP 7 wird erweitert [119], um C++ zu umfassen, indem die relevanten Teile der C++-Styleguides von Unladen Swallow [69], LLVM [70] und Google [71] übernommen werden.
  • Unterschiedliche C++-Compiler erzeugen unterschiedliche ABIs; dies kann Probleme verursachen, wenn CPython mit einem C++-Compiler kompiliert wird und Erweiterungsmodule mit einem anderen C++-Compiler kompiliert werden.

Verwaltung von LLVM-Releases, C++-API-Änderungen

LLVM wird regelmäßig alle sechs Monate veröffentlicht. Das bedeutet, dass LLVM während der Entwicklung einer CPython 3.x-Version zwei- oder dreimal veröffentlicht werden kann. Jede LLVM-Veröffentlichung bringt neuere und leistungsfähigere Optimierungen, verbesserte Plattformunterstützung und anspruchsvollere Codegenerierung.

LLVM-Releases enthalten normalerweise inkompatible Änderungen an der LLVM C++ API; die Release Notes für LLVM 2.6 [9] enthalten eine Liste von absichtlich eingeführten Inkompatibilitäten. Unladen Swallow hat den LLVM-Trunk während der Entwicklung eng verfolgt. Unsere Erfahrung ist, dass LLVM API-Änderungen offensichtlich und leicht oder mechanisch zu beheben sind. Wir fügen zwei solche Änderungen aus dem Unladen Swallow-Baum als Referenzen hinzu: [10], [11].

Aufgrund von API-Inkompatibilitäten empfehlen wir, dass ein LLVM-basierter CPython die Kompatibilität mit einer einzigen LLVM-Version gleichzeitig anstrebt. Dies wird den Aufwand für das Kernentwicklungsteam reduzieren. Die Bindung an eine LLVM-Version sollte aus Sicht des Paketmanagements kein Problem darstellen, da vorab kompilierte LLVM-Pakete in der Regel recht schnell nach einer LLVM-Veröffentlichung über Standard-Systempaketmanager verfügbar werden und andernfalls stellt llvm.org selbst Binärpakete bereit.

Unladen Swallow enthielt historisch eine Kopie der LLVM- und Clang-Source-Trees im Unladen Swallow-Baum; dies geschah, um uns zu ermöglichen, den LLVM-Trunk eng zu verfolgen, während wir Patches daran vornahmen. Wir empfehlen dieses Entwicklungsmodell für CPython nicht. CPython-Releases sollten auf offiziellen LLVM-Releases basieren. Vorab kompilierte LLVM-Pakete sind für Darwin über MacPorts [12] und für die meisten großen Linux-Distributionen verfügbar ([13], [14], [16]). LLVM selbst bietet zusätzliche Binärdateien, z. B. für MinGW [25].

LLVM ist derzeit für statisches Linken vorgesehen; dies bedeutet, dass Binärpakete von CPython die relevanten Teile (nicht alle!) von LLVM enthalten werden. Dies wird die Binärgröße erhöhen, wie oben erwähnt. Um die nachgelagerte Paketverwaltung zu vereinfachen, werden wir LLVM modifizieren, um die dynamische Verlinkung besser zu unterstützen. Dieses Problem wird die endgültige Zusammenführung blockieren [97].

Unladen Swallow hat einen Vollzeit-Ingenieur beauftragt, alle verbleibenden kritischen Probleme in LLVM vor der LLVM 2.7-Veröffentlichung zu beheben. Wir halten es für unerlässlich, dass CPython 3.x auf einer veröffentlichten Version von LLVM basieren kann, anstatt wie Unladen Swallow eng den LLVM-Trunk zu verfolgen. Wir glauben, dass wir diese Arbeit [98] vor der Veröffentlichung von LLVM 2.7, erwartet im Mai 2010, abschließen werden.

CPython kompilieren

Zusätzlich zu einer Laufzeitabhängigkeit von LLVM enthält Unladen Swallow eine Build-Zeitabhängigkeit von Clang [5], einem LLVM-basierten C/C++-Compiler. Wir verwenden dies, um Teile der C-Sprachen-Python-Laufzeit in LLVMs Zwischenrepräsentation zu kompilieren; dies ermöglicht uns Cross-Language-Inlining, was zu erhöhter Leistung führt. Clang ist nicht erforderlich, um Unladen Swallow auszuführen. Clang Binärpakete sind von den meisten großen Linux-Distributionen verfügbar (z. B. [15]).

Wir haben die Auswirkungen von Unladen Swallow auf die Zeit zum Kompilieren von Python untersucht, einschließlich Konfigurieren, vollständiger Kompilierung und inkrementeller Kompilierung nach dem Berühren einer einzelnen C-Quelldatei.

./configure CPython 2.6.4 CPython 3.1.1 Unladen Swallow r988
Durchlauf 1 0m20.795s 0m16.558s 0m15.477s
Durchlauf 2 0m15.255s 0m16.349s 0m15.391s
Durchlauf 3 0m15.228s 0m16.299s 0m15.528s
Vollständiger make CPython 2.6.4 CPython 3.1.1 Unladen Swallow r988
Durchlauf 1 1m30.776s 1m22.367s 1m54.053s
Durchlauf 2 1m21.374s 1m22.064s 1m49.448s
Durchlauf 3 1m22.047s 1m23.645s 1m49.305s

Vollständige Builds erleiden Einbußen aufgrund von a) zusätzlichen .cc-Dateien, die für die LLVM-Interaktion benötigt werden, b) statischer Verlinkung von LLVM in libpython, c) Kompilierung von Teilen der Python-Laufzeit in LLVM IR, um Cross-Language-Inlining zu ermöglichen.

Inkrementelle Builds sind ebenfalls etwas langsamer als das Mainline CPython. Die folgende Tabelle zeigt die inkrementellen Neubauzeiten nach dem Berühren von Objects/listobject.c.

Incr make CPython 2.6.4 CPython 3.1.1 Unladen Swallow r1024
Durchlauf 1 0m1.854s 0m1.456s 0m6.680s
Durchlauf 2 0m1.437s 0m1.442s 0m5.310s
Durchlauf 3 0m1.440s 0m1.425s 0m7.639s

Wie bei vollständigen Builds stammt diese zusätzliche Zeit aus der statischen Verlinkung von LLVM in libpython. Wenn libpython dynamisch gegen LLVM gelinkt wäre, würde dieser Overhead reduziert.

Vorgeschlagener Zusammenführungsplan

Wir schlagen vor, unsere Bemühungen auf die endgültige Zusammenführung mit der 3.x-Entwicklungslinie von CPython zu konzentrieren. Der BDFL hat angedeutet, dass 2.7 die endgültige Veröffentlichung der 2.x-Entwicklungslinie von CPython sein wird [30], und da 2.7 Alpha 1 bereits veröffentlicht wurde, haben wir das Fenster verpasst. Python 3 ist die Zukunft, und dort werden wir unsere Leistungsbemühungen ansetzen.

Wir empfehlen den folgenden Plan für die Zusammenführung von Unladen Swallow in den CPython-Quellcodebaum

  • Erstellung eines Zweigs im CPython SVN-Repository zur Arbeit, nennen wir ihn beispielhaft py3k-jit. Dies wird ein Zweig des CPython py3k-Zweigs sein.
  • Wir werden diesen Zweig eng mit py3k integriert halten. Je weiter wir abweichen, desto schwieriger wird unsere Arbeit.
  • Alle JIT-bezogenen Patches werden in den py3k-jit-Zweig eingehen.
  • Nicht-JIT-bezogene Patches werden in den py3k-Zweig eingehen (nach Überprüfung und Genehmigung) und zurück in den py3k-jit-Zweig zusammengeführt.
  • Potenziell kontroverse Themen, wie die Einführung neuer Kommandozeilenflags oder Umgebungsvariablen, werden auf python-dev diskutiert.

Da Google CPython 2.x intern verwendet, basiert Unladen Swallow auf CPython 2.6. Wir müssten unseren Compiler auf Python 3 portieren; dies würde als Patches auf den py3k-jit-Zweig angewendet werden, so dass der Zweig jederzeit eine konsistente Implementierung von Python 3 bleibt.

Wir glauben, dass dieser Ansatz den Prozess der Veröffentlichung 3.2 oder 3.3 minimal stören wird, während wir alle verbleibenden Probleme ausbügeln, die die endgültige Zusammenführung in py3k blockieren. Unladen Swallow unterhält eine Liste bekannter Probleme, die vor der endgültigen Zusammenführung erforderlich sind [31], die alle in diesem PEP genannten Probleme enthält; wir vertrauen darauf, dass die CPython-Community eigene Bedenken haben wird. Diese Liste ist nicht statisch; in Zukunft können weitere Probleme auftreten, die die endgültige Zusammenführung in den py3k-Zweig blockieren.

Änderungen werden direkt in den py3k-jit-Zweig committed, wobei nur große, schwierige oder kontroverse Änderungen zur Code-Review vor dem Commit eingereicht werden.

Notfallpläne

Es besteht die Möglichkeit, dass wir den Speicherverbrauch oder die Startzeit nicht auf ein für die CPython-Community zufriedenstellendes Niveau reduzieren können. Unser primärer Notfallplan für diese Situation ist die Umstellung von einer Online-Just-in-Time-Kompilierungsstrategie auf eine Offline-Ahead-of-Time-Strategie unter Verwendung einer instrumentierten CPython-Interpreter-Schleife zur Gewinnung von Feedback. Dies ist dasselbe Modell, das von GCCs Feedback-Directed Optimizations (-fprofile-generate) [111] und Microsoft Visual Studios Profil-gesteuerten Optimierungen [112] verwendet wird; wir werden uns hier auf „Feedback-Directed Optimization“ oder FDO beziehen.

Wir glauben, dass ein FDO-Compiler für Python einem JIT-Compiler unterlegen wäre. FDO erfordert eine qualitativ hochwertige, repräsentative Benchmark-Suite, die sowohl in der Open-Source- als auch in der Closed-Source-Entwicklung relativ selten ist. Ein JIT-Compiler kann dynamisch die Hotspots in jeder Anwendung finden und optimieren – Benchmark-Suite oder nicht – und so Anpassungen an Änderungen in Anwendungsengpässen ohne menschliches Eingreifen ermöglichen.

Wenn ein Ahead-of-Time FDO-Compiler benötigt wird, sollte er einen großen Teil des Codes und der Infrastruktur nutzen können, die bereits für den JIT-Compiler von Unladen Swallow entwickelt wurden. Tatsächlich könnten diese beiden Kompilierungsstrategien nebeneinander existieren.

Zukünftige Arbeit

Ein JIT-Compiler ist ein äußerst flexibles Werkzeug, und wir haben sein volles Potenzial bei weitem noch nicht ausgeschöpft. Unladen Swallow pflegt eine Liste von noch zu implementierenden Leistungsoptimierungen [50], für deren vollständige Implementierung das Team noch keine Zeit hatte. Beispiele

  • Python/Python Inlining [66]. Unser Compiler führt derzeit keine Inlinings zwischen reinen Python-Funktionen durch. Die Arbeit daran ist im Gange [68].
  • Unboxing [67]. Unboxing ist entscheidend für die numerische Leistung. Insbesondere PyPy hat den Wert des Unboxing für stark numerische Workloads gezeigt.
  • Neukompilierung, Anpassung. Unladen Swallow kompiliert eine Python-Funktion derzeit nur einmal, basierend auf ihrem bisherigen Nutzungsmuster. Wenn sich das Nutzungsmuster ändert, verhindern Einschränkungen in LLVM [72], dass wir die Funktion neu kompilieren, um dem neuen Nutzungsmuster besser zu dienen.
  • JIT-Kompilierung von regulären Ausdrücken. Moderne JavaScript-Engines nutzen ihre JIT-Kompilierungsinfrastruktur wieder, um die Regex-Leistung zu steigern [73]. Unladen Swallow hat Benchmarks für die Leistung von Python-Regulären Ausdrücken entwickelt ([74], [75], [76]), aber die Arbeit an der Regex-Leistung befindet sich noch in einem frühen Stadium [77].
  • Trace-Kompilierung [91], [92]. Basierend auf den Ergebnissen von PyPy und Tracemonkey [93] glauben wir, dass ein CPython JIT die Trace-Kompilierung bis zu einem gewissen Grad beinhalten sollte. Wir haben anfänglich einen rein-tracing JIT-Compiler zugunsten eines einfacheren, Funktions-für-Funktions-Compilers vermieden. Dieser Funktions-für-Funktions-Compiler hat jedoch die Grundlage für einen zukünftigen Tracing-Compiler geschaffen, der in denselben Begriffen implementiert ist.
  • Profil-Generierung/Wiederverwendung. Die von der JIT gesammelten Laufzeitdaten könnten auf die Festplatte geschrieben und von nachfolgenden JIT-Kompilierungen oder von externen Werkzeugen wie Cython [101] oder einem Feedback-gestützten Code-Coverage-Tool wiederverwendet werden.

Diese Liste ist keineswegs erschöpfend. Es gibt eine riesige Literatur über Optimierungen für dynamische Sprachen, die in Bezug auf den LLVM-basierten JIT-Compiler von Unladen Swallow implementiert werden könnten und sollten [54].

Unladen Swallow Community

Wir möchten uns bei der Gemeinschaft der Entwickler bedanken, die zu Unladen Swallow beigetragen haben, insbesondere: James Abbatiello, Joerg Blank, Eric Christopher, Alex Gaynor, Chris Lattner, Nick Lewycky, Evan Phoenix und Thomas Wouters.

Lizenzierung

Alle Arbeiten an Unladen Swallow sind der Python Software Foundation (PSF) unter den Bedingungen der Python Software Foundation License v2 [56] im Rahmen der Sammelvereinbarung von Google mit der PSF lizenziert.

LLVM ist unter der University of Illinois/NCSA Open Source License [57] [58] lizenziert, einer liberalen, OSI-zugelassenen Lizenz. Die University of Illinois Urbana-Champaign ist der alleinige Copyright-Inhaber von LLVM.

Referenzen


Quelle: https://github.com/python/peps/blob/main/peps/pep-3146.rst

Zuletzt geändert: 2025-01-30 01:22:16 GMT