Einführung
Die Programmiersprache Python ist eine Schnittstelle, die auf vielfältige Weise implementiert werden kann. Beispiele hierfür sind CPython, das auf der Programmiersprache C basiert, Jython, das mit Java implementiert ist, usw.
Obwohl CPython die beliebteste Python-Implementierung ist, zählt sie nicht zu den schnellsten. PyPy ist eine alternative Python-Implementierung, die sowohl kompatibel als auch schnell ist. PyPy nutzt die JIT-Kompilierung, wodurch die Ausführungszeit langlaufender Operationen drastisch reduziert wird.
In diesem Tutorial stellen wir PyPy für Anfänger vor und heben die Unterschiede zu CPython hervor. Wir besprechen außerdem die Vorteile und Grenzen von PyPy. Anschließend zeigen wir, wie man PyPy herunterlädt und damit ein einfaches Python-Skript ausführt.
Im Einzelnen umfasst diese Schulung Folgendes:
- Ein kurzer Überblick über CPython
- Einführung in PyPy und seine Funktionen
- PyPy-Beschränkungen
- PyPy unter Ubuntu ausführen
- PyPy vs. CPython-Laufzeit
Ein kurzer Überblick über CPython
Bevor wir PyPy besprechen, ist es wichtig zu verstehen, wie CPython funktioniert. Unten sehen Sie eine Abbildung der Ausführungspipeline eines mit CPython implementierten Python-Skripts.
Ausgehend von einem Python-Skript (Datei mit der Endung .py) wird der Quellcode zunächst mit dem CPython-Compiler in Bytecode kompiliert. Der Bytecode wird generiert und in einer Datei mit der Endung .pyc gespeichert. Anschließend wird der Bytecode in einer virtuellen Umgebung mithilfe des CPython-Interpreters ausgeführt.
Die Verwendung eines Compilers zur Umwandlung von Quellcode in Bytecode bietet Vorteile. Ohne Compiler arbeitet der Interpreter direkt mit dem Quellcode und übersetzt ihn Zeile für Zeile in Maschinencode. Der Nachteil dabei ist, dass für jede Zeile des Quellcodes Prozesse erforderlich sind, die für jede Zeile wiederholt werden. Beispielsweise wird die Syntaxanalyse auf jede Zeile unabhängig von den anderen angewendet, wodurch der Interpreter viel Zeit mit der Übersetzung verbringt. Der Compiler löst dieses Problem, da er den gesamten Code auf einmal verarbeiten kann und die Syntaxanalyse daher nur einmal statt für jede Zeile durchgeführt wird. Der vom Compiler erzeugte Bytecode ist somit leicht interpretierbar. Es ist zu beachten, dass die Kompilierung des gesamten Quellcodes in manchen Fällen nicht sinnvoll sein kann. Ein anschauliches Beispiel hierfür wird bei der Besprechung von PyPy gezeigt.
Sobald der Bytecode generiert ist, wird er vom Interpreter in der virtuellen Maschine ausgeführt. Die virtuelle Umgebung ist vorteilhaft, da sie den CPython-Bytecode von der Maschine entkoppelt und Python somit plattformübergreifend nutzbar macht.
Leider reicht es nicht aus, Bytecode mit einem Compiler zu generieren, um die CPython-Ausführung zu beschleunigen. Der Interpreter übersetzt den Code bei jeder Ausführung in Maschinencode. Dauert die Ausführung einer Zeile beispielsweise LX Sekunden, so dauert die zehnfache Ausführung X*10 Sekunden. Bei langlaufenden Operationen ist dies sehr zeitaufwendig.
Ausgehend von den CPython-Bugs werfen wir nun einen Blick auf PyPy.
Einführung in PyPy und seine Funktionen
PyPy ist eine Python-Implementierung ähnlich wie CPython, die sowohl kompatibel als auch schnell ist. “Kompatibel” bedeutet, dass PyPy mit CPython kompatibel ist, d. h. Sie können fast alle CPython-Befehle in PyPy verwenden. Wie bereits erwähnt, gibt es einige Unterschiede in der Kompatibilität. PyPys größter Vorteil ist seine Geschwindigkeit. PyPy ist deutlich schneller als CPython. Wir werden später einige Tests sehen, in denen PyPy etwa siebenmal schneller ist. In manchen Fällen kann es sogar um ein Vielfaches schneller sein als CPython. Wie erreicht PyPy diese Geschwindigkeit?
Geschwindigkeit
PyPy verwendet einen Just-in-Time-Compiler (JIT), der Python-Skripte deutlich beschleunigen kann. CPython hingegen nutzt Ahead-of-Time-Kompilierung (AOT), d. h. der gesamte Code wird vor der Ausführung in Bytecode übersetzt. Der JIT-Compiler übersetzt den Code erst zur Laufzeit, also nur dann, wenn es nötig ist.
Der Quellcode kann Codeblöcke enthalten, die gar nicht ausgeführt, aber dennoch mit dem AOT-Compiler übersetzt werden. Dies führt zu längeren Verarbeitungszeiten. Bei großem Quellcode mit Tausenden von Zeilen macht die Verwendung von JIT einen großen Unterschied. Mit AOT wird der gesamte Quellcode übersetzt, was viel Zeit in Anspruch nimmt. Mit JIT werden nur die benötigten Teile des Codes ausgeführt, wodurch die Verarbeitung deutlich beschleunigt wird.
Nachdem PyPy einen Codeabschnitt übersetzt hat, wird dieser zwischengespeichert. Das bedeutet, dass der Code nur einmal übersetzt wird und die Übersetzung später wiederverwendet wird. Der CPython-Interpreter hingegen wiederholt die Übersetzung bei jeder Codeausführung, was ein weiterer Grund für seine Langsamkeit ist.
Mühelos
PyPy ist nicht die einzige Möglichkeit, die Performance von Python-Skripten zu steigern, aber die einfachste. Beispielsweise kann Cython verwendet werden, um die Zuweisung von C-Datentypen zu Variablen zu beschleunigen. Das Problem dabei ist, dass Cython eine manuelle Überprüfung und Optimierung des Quellcodes erfordert. Dies ist mühsam und wird mit zunehmender Codegröße immer komplexer. Mit PyPy hingegen wird regulärer Python-Code ohne jeglichen Aufwand deutlich schneller ausgeführt.
Kein Stapel
Standard-Python verwendet den C-Stack. Dieser Stack speichert die Abfolge der Funktionen, die voneinander aufgerufen werden (Rückgabewerte). Da die Größe des Stacks begrenzt ist, ist auch die Anzahl der Funktionsaufrufe begrenzt.
PyPy verwendet Stackless Python, eine Python-Implementierung, die ohne den C-Stack auskommt. Stattdessen werden Funktionsaufrufe zusammen mit Objekten auf dem Stack gespeichert. Da der Stack größer als der Heap ist, können mehr Funktionsaufrufe durchgeführt werden.
Stackless Python unterstützt auch Mikrothreads, die effizienter als reguläre Python-Threads sind. In einem Stackless-Python-Thread können Tausende von Aufgaben, sogenannte «Tasklets», ausgeführt werden, die alle in einem einzigen Thread laufen.
Mithilfe von Tasklets können Aufgaben parallel ausgeführt werden. Parallelität bedeutet, dass zwei Aufgaben gleichzeitig laufen und dieselben Ressourcen nutzen. Eine Aufgabe läuft eine Zeit lang und wird dann unterbrochen, um Platz für die zweite Aufgabe zu schaffen. Dies unterscheidet sich von Parallelität, bei der zwei separate, aber gleichzeitig ausgeführte Aufgaben laufen.
Durch die Verwendung von Tasklets wird die Anzahl der erstellten Threads reduziert, wodurch der Verwaltungsaufwand des Betriebssystems für diese Threads sinkt. Folglich benötigt die Beschleunigung der Ausführung durch Umschalten zwischen zwei Threads mehr Zeit als das Umschalten zwischen zwei Tasks.
Die Verwendung von Stackless Python ermöglichte auch die Nutzung von Continuations. Mit Continuations können wir den Zustand einer Aufgabe speichern und später wiederherstellen, um die Aufgabe fortzusetzen. Stackless Python unterscheidet sich nicht von Standard-Python, sondern bietet lediglich zusätzliche Funktionen. Alles, was in Standard-Python verfügbar ist, ist auch in Stackless Python verfügbar.
Nachdem wir die Vorteile von PyPy besprochen haben, wollen wir im nächsten Abschnitt auf seine Grenzen eingehen.
PyPy-Beschränkungen
Während CPython auf jeder Maschine und jeder CPU-Architektur eingesetzt werden kann, ist die Unterstützung für PyPy vergleichsweise eingeschränkt.
Hier sind die von PyPy unterstützten und gepflegten CPU-Architekturen (Quelle):
- x86 (IA-32) und x86_64
- ARM-Plattformen (ARMv6 oder ARMv7, mit VFPv3)
- AArch64
- PowerPC 64-Bit, sowohl Little- als auch Big-Endian
- System Z (s390x)
PyPy funktioniert nicht auf allen Linux-Distributionen. Verwenden Sie daher unbedingt eine der unterstützten Distributionen. Die Ausführung der PyPy-Linux-Binärdatei auf einer nicht unterstützten Distribution führt zu einem Fehler. PyPy unterstützt nur eine Version von Python 2 und Python 3: PyPy 2.7 und PyPy 3.6.
Wenn der in PyPy ausgeführte Code reiner Python-Code ist, ist die durch PyPy erzielte Beschleunigung in der Regel signifikant. Enthält der Code jedoch C-Erweiterungen wie NumPy, kann PyPy die Laufzeit sogar verlängern. Das PyPy-Projekt wird aktiv weiterentwickelt und wird daher zukünftig möglicherweise eine bessere Unterstützung für C-Erweiterungen bieten.
PyPy wird von vielen gängigen Python-Frameworks, wie beispielsweise Kivy, nicht unterstützt. Kivy ermöglicht es CPython, auf allen Plattformen, einschließlich Android und iOS, zu laufen. Das bedeutet, dass PyPy nicht auf Mobilgeräten ausgeführt werden kann.
Nachdem wir nun die Vorteile und Grenzen von PyPy kennengelernt haben, erklären wir im Folgenden, wie man PyPy unter Ubuntu ausführt.
PyPy unter Ubuntu ausführen
PyPy kann unter Mac, Linux oder Windows ausgeführt werden, wir konzentrieren uns hier jedoch auf die Ausführung unter Ubuntu. Wichtig: PyPy-Binärdateien für Linux werden nur von bestimmten Linux-Distributionen unterstützt. Eine Liste der verfügbaren PyPy-Binärdateien und der unterstützten Distributionen finden Sie auf dieser Seite. Beispielsweise wird PyPy (bzw. Python 2.7 oder Python 3.6) nur von drei Ubuntu-Versionen unterstützt: 18.04, 16.04 und 14.04. Wenn Sie die aktuellste Ubuntu-Version (19.10) verwenden, können Sie PyPy nicht darauf ausführen. Der Versuch, PyPy auf einer nicht unterstützten Distribution auszuführen, führt zu folgendem Fehler:
PyPy-Binärdateien werden als komprimierte Dateien bereitgestellt. Sie müssen lediglich die heruntergeladene Datei entpacken. Im entpackten Ordner befindet sich ein Unterordner namens „bin“, in dem die ausführbare PyPy-Datei zu finden ist. Ich verwende Python 3.6, daher heißt die Datei „pypy3“. Für Python 2.7 heißt sie einfach „pypy“.
Um Python 3 unter CPython im Terminal auszuführen, geben Sie einfach den Befehl `python3` ein. Um PyPy auszuführen, geben Sie einfach den Befehl `pypy3` ein.
Wie in der folgenden Abbildung dargestellt, kann die Eingabe des Befehls `pypy3` im Terminal die Meldung ‘pypy3 nicht gefunden’ ausgeben. Dies liegt daran, dass der Pfad zu PyPy nicht zur Umgebungsvariablen PATH hinzugefügt wurde. Der Befehl `./pypy3` funktioniert jedoch, vorausgesetzt, der aktuelle Pfad im Terminal befindet sich im PyPy-Binärverzeichnis. Der Punkt `.` steht für das aktuelle Verzeichnis, und der Schrägstrich `/` greift auf Dateien und Ordner im aktuellen Verzeichnis zu. Die Ausführung des Befehls `./pypy3` startet Python erfolgreich.
Sie können nun wie gewohnt mit Python arbeiten und dabei PyPy nutzen. Beispielsweise können wir ein einfaches Python-Skript erstellen, das 1000 Zahlen addiert, und es mit PyPy ausführen. Der Code lautet wie folgt.
nums = range(1000)
sum = 0
for k in nums:
sum = sum + k
print("Sum of 1,000 numbers is : ", sum)Wenn dieses Skript test.py heißt, können Sie es einfach mit dem folgenden Befehl ausführen (vorausgesetzt, die Python-Datei befindet sich im PyPy-Bin-Ordner, also am selben Ort wie der pypy3-Befehl).
./pypy3 test.py
Die nächste Abbildung zeigt das Ergebnis der Ausführung des vorherigen Codes.
PyPy vs. CPython-Laufzeit
Um die Ausführungszeit von PyPy und CPython für die Summe von 1000 Zahlen zu vergleichen, ändert sich der Code zur Zeitmessung wie folgt.
import time
t1 = time.time()
nums = range(1000)
sum = 0
for k in nums:
sum = sum + k
print("Sum of 1,000 numbers is : ", sum)
t2 = time.time()
t = t2 - t1
print("Elapsed time is : ", t, " seconds")Für PyPy liegt die Laufzeit bei etwa 0,00045 Sekunden, verglichen mit 0,0002 Sekunden für CPython (ich habe den Code auf meinem Core i7-6500U-Rechner mit 2,5 GHz ausgeführt). In diesem Fall benötigt CPython weniger Zeit als PyPy, was zu erwarten ist, da es sich nicht um eine wirklich langwierige Aufgabe handelt. Würde der Code jedoch 1 Million statt 1000 Zahlen addieren, wäre PyPy letztendlich schneller. In diesem Fall bräuchte PyPy 0,00035 Sekunden und CPython 0,1 Sekunden. Der Vorteil von PyPy ist nun deutlich. Dies verdeutlicht, wie viel langsamer CPython bei langwierigen Aufgaben ist.
Ergebnis
Dieses Tutorial stellt PyPy vor, die schnellste Python-Implementierung. Der Hauptvorteil von PyPy liegt in der Just-in-Time-Kompilierung (JIT), die den kompilierten Maschinencode zwischenspeichert, um eine erneute Ausführung zu verhindern. Auch die Grenzen von PyPy werden aufgezeigt; die wichtigste ist, dass es zwar gut für reinen Python-Code funktioniert, aber für C-Erweiterungen nicht effizient ist.
Wir haben auch untersucht, wie PyPy unter Ubuntu läuft, und die Ausführungszeiten von CPython und PyPy verglichen. Dabei zeigte sich PyPys Leistungsfähigkeit bei langlaufenden Aufgaben. Bei kurzlaufenden Aufgaben könnte CPython jedoch weiterhin die Nase vorn haben. Weitere Vergleiche zwischen PyPy, CPython und Cython werden wir in zukünftigen Artikeln anstellen.












