Erste Schritte mit PyPy
Die Programmiersprache Python ist eine Schnittstelle, die auf verschiedene Weise implementiert werden kann. Einige Beispiele sind CPython, das die C-Sprache verwendet, Jython, das mit Java implementiert ist, und so weiter.
Obwohl CPython am beliebtesten ist, ist es nicht das schnellste. PyPy ist eine alternative Python-Implementierung, die sowohl konform als auch schnell ist. PyPy nutzt die Just-in-Time (JIT)-Kompilierung, die die Ausführungszeit für lang laufende Operationen drastisch reduziert.
In diesem Tutorial wird PyPy für Anfänger vorgestellt, um hervorzuheben, wie es sich von CPython unterscheidet. Wir werden auch seine Vorteile und Einschränkungen behandeln. Danach schauen wir uns an, wie man PyPy herunterlädt und ein einfaches Python-Skript ausführt.
Konkret behandelt dieses Tutorial Folgendes:
- Ein kurzer Überblick über CPython
- Einführung in PyPy und seine Funktionen
- PyPy-Einschränkungen
- Ausführen von PyPy auf Ubuntu
- Ausführungszeit von PyPy vs. CPython
Legen wir los.
Ein kurzer Überblick über CPython
Bevor wir PyPy besprechen, ist es wichtig zu wissen, wie CPython funktioniert. Unten sehen Sie eine Visualisierung der Ausführungspipeline eines Python-Skripts, das mit CPython implementiert wurde.
Kompilierung von Python-Code
Ein gegebenes Python-.py-Skript wird 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 mit dem CPython-Interpreter in einer virtuellen Umgebung ausgeführt.
Vorteile der Bytecode-Kompilierung
Es gibt Vorteile bei der Verwendung des Compilers zur Umwandlung des Quellcodes in Bytecode. Wenn kein Compiler verwendet wird, dann arbeitet der Interpreter direkt mit dem Quellcode, indem er ihn zeilenweise in Maschinencode übersetzt. Der Nachteil dieser Methode besteht darin, dass bestimmte Prozesse für jede Zeile des Quellcodes wiederholt werden müssen, um sie in Maschinencode zu übersetzen. Beispielsweise wird die Syntaxanalyse für jede Zeile unabhängig von den anderen Zeilen durchgeführt, was dazu führt, dass der Interpreter viel Zeit für die Übersetzung des Codes benötigt.
Der Compiler löst dieses Problem, indem er den gesamten Code auf einmal verarbeitet. Dadurch wird die Syntaxanalyse nur einmal durchgeführt, anstatt für jede einzelne Zeile separat. Der vom Compiler erzeugte Bytecode kann daher effizient interpretiert werden. Beachten Sie jedoch, dass die vollständige Kompilierung des Quellcodes in manchen Fällen nicht hilfreich sein kann – ein klares Beispiel dafür werden wir später bei der Diskussion über PyPy sehen.
Ausführung von Bytecode in einer virtuellen Umgebung
Nachdem der Bytecode generiert wurde, wird er vom Interpreter in der virtuellen Maschine ausgeführt. Die virtuelle Umgebung isoliert den CPython-Bytecode von der Maschine und macht Python so plattformunabhängig.
Leistungsbeschränkungen von CPython
Die Verwendung eines Compilers zur Bytecode-Erstellung reicht jedoch nicht aus, um die Ausführung von CPython zu beschleunigen. Der Interpreter übersetzt den Code jedes Mal, wenn er ausgeführt wird, in Maschinencode. Das bedeutet, dass eine Zeile L, die X Sekunden zur Ausführung benötigt, bei zehnfacher Ausführung X*10 Sekunden benötigt. Für lang laufende Operationen ist dies zu teuer in der Ausführungszeit.
Aufgrund dieser Nachteile von CPython schauen wir uns nun PyPy an.
Einführung in PyPy und seine Funktionen
PyPy ist eine Python-Implementierung ähnlich zu CPython, die sowohl konform als auch schnell ist. „Konform“ bedeutet, dass PyPy mit CPython kompatibel ist, sodass fast alle CPython-Syntaxen in PyPy verwendet werden können. Es gibt einige Kompatibilitätsunterschiede, wie hier beschrieben. Der größte Vorteil von PyPy ist seine Geschwindigkeit. PyPy ist viel schneller als CPython; später sehen wir Tests, bei denen PyPy etwa siebenmal schneller ist. In manchen Fällen kann es sogar um ein Vielfaches schneller sein. Aber wie erreicht PyPy diese Geschwindigkeit?
Geschwindigkeit
PyPy verwendet einen Just-in-Time (JIT)-Compiler, der die Geschwindigkeit von Python-Skripten drastisch erhöhen kann. Die Art der Kompilierung, die in CPython verwendet wird, ist Ahead-of-Time (AOT), was bedeutet, dass der gesamte Code vor der Ausführung in Bytecode übersetzt wird. JIT hingegen übersetzt den Code erst zur Laufzeit – nur dann, wenn er tatsächlich benötigt wird.
Der Quellcode kann Codeblöcke enthalten, die gar nicht ausgeführt werden, aber dennoch vom AOT-Compiler übersetzt werden. Dies führt zu langsameren Verarbeitungszeiten. Wenn der Quellcode groß ist und Tausende von Zeilen enthält, macht die Verwendung eines JIT-Compilers einen großen Unterschied. Bei AOT wird der gesamte Quellcode übersetzt, was viel Zeit in Anspruch nimmt. Bei JIT werden nur die tatsächlich benötigten Teile des Codes ausgeführt, was den Prozess erheblich beschleunigt.
Nachdem PyPy einen Teil des Codes übersetzt hat, wird dieser im Cache gespeichert. Das bedeutet, dass der Code nur einmal übersetzt wird und die Übersetzung später wiederverwendet wird. Der CPython-Interpreter hingegen wiederholt die Übersetzung jedes Mal, wenn der Code ausgeführt wird, was zusätzlich zu seiner Langsamkeit beiträgt.
Einfachheit
PyPy ist nicht die einzige Möglichkeit, die Leistung von Python-Skripten zu verbessern – aber es ist die einfachste. Alternativen wie Cython erfordern manuelle Optimierungen, während PyPy den regulären Python-Code einfach schneller ausführt.
Stackless
Standard-Python verwendet den C-Stack. Dieser Stack speichert die Reihenfolge der Funktionsaufrufe, die sich gegenseitig aufrufen (Rekursion). Da die Größe des Stacks begrenzt ist, ist auch die Anzahl der möglichen Funktionsaufrufe eingeschränkt.
PyPy verwendet Stackless Python, eine Python-Implementierung, die nicht den C-Stack nutzt. Stattdessen werden die Funktionsaufrufe im Heap neben den Objekten gespeichert. Da die Größe des Heaps größer ist als die des Stacks, können mehr Funktionsaufrufe durchgeführt werden.
Stackless Python unterstützt außerdem Mikro-Threads, die effizienter sind als reguläre Python-Threads. Innerhalb eines einzelnen Stackless-Python-Threads können Tausende von Aufgaben, sogenannte „Tasklets“, ausgeführt werden – alle innerhalb desselben Threads.
Die Verwendung von Tasklets ermöglicht das gleichzeitige Ausführen von Aufgaben. Nebenläufigkeit bedeutet, dass zwei Aufgaben gleichzeitig arbeiten, indem sie dieselben Ressourcen gemeinsam nutzen. Eine Aufgabe wird für eine gewisse Zeit ausgeführt, dann angehalten, um Platz für die zweite Aufgabe zu machen. Dies unterscheidet sich von Parallelität, bei der beide Aufgaben unabhängig voneinander gleichzeitig ausgeführt werden.
Die Nutzung von Tasklets reduziert die Anzahl der erstellten Threads und verringert somit den Verwaltungsaufwand für das Betriebssystem. Das Umschalten zwischen zwei Threads erfordert mehr Rechenzeit als das Umschalten zwischen zwei Tasklets, was die Ausführung beschleunigt.
Stackless Python ermöglichte zudem die Implementierung von Continuations. Continuations erlauben es, den Zustand einer Aufgabe zu speichern und später wiederherzustellen, um sie fortzusetzen. Stackless Python unterscheidet sich nicht grundlegend von Standard-Python – es erweitert lediglich dessen Funktionalität. Alles, was in Standard-Python verfügbar ist, ist auch in Stackless Python nutzbar.
Nachdem wir die Vorteile von PyPy besprochen haben, werfen wir nun einen Blick auf seine Einschränkungen.
PyPy-Einschränkungen
Obwohl CPython auf jeder Maschine und jeder CPU-Architektur laufen kann, ist die Unterstützung durch PyPy begrenzter.
Unterstützte Architekturen (Quelle):
- x86 (IA-32) und x86_64
- ARM-Plattformen (ARMv6 oder ARMv7, mit VFPv3)
- AArch64
- PowerPC 64bit, sowohl Little als auch Big Endian
- System Z (s390x)
PyPy kann nicht auf allen Linux-Distributionen ausgeführt werden. Daher muss darauf geachtet werden, eine unterstützte Version zu verwenden. Wenn eine nicht unterstützte Linux-Distribution verwendet wird, gibt die PyPy-Binärdatei eine Fehlermeldung aus.
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 reines Python ist, ist die Geschwindigkeitssteigerung durch PyPy in der Regel deutlich spürbar. Enthält der Code jedoch C-Erweiterungen wie NumPy, kann PyPy die Ausführungszeit sogar verlängern. Das PyPy-Projekt wird aktiv weiterentwickelt, sodass es in Zukunft möglicherweise eine bessere Unterstützung für C-Erweiterungen bieten wird.
PyPy wird zudem von einigen bekannten Python-Frameworks nicht unterstützt, darunter Kivy. 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 Einschränkungen von PyPy kennengelernt haben, sehen wir uns an, wie PyPy unter Ubuntu ausgeführt wird.
PyPy auf Ubuntu ausführen
PyPy kann unter Mac, Linux oder Windows ausgeführt werden, aber in diesem Abschnitt konzentrieren wir uns auf die Nutzung unter Ubuntu. Es ist wichtig zu betonen, dass die PyPy-Linux-Binärdateien nur für bestimmte Linux-Distributionen unterstützt werden. Die verfügbaren PyPy-Binärdateien und deren unterstützte Distributionen können auf dieser Seite überprüft werden.
Zum Beispiel wird PyPy (sowohl Python 2.7 als auch Python 3.6) nur für drei Ubuntu-Versionen unterstützt: 18.04, 16.04 und 14.04. Wenn Sie die neueste Ubuntu-Version bis zum aktuellen Zeitpunkt (19.10) verwenden, kann PyPy darauf nicht ausgeführt werden. Der Versuch, PyPy auf einer nicht unterstützten Distribution auszuführen, führt zu folgendem Fehler:
pypy: error while loading shared libraries ...
Ich verwende einfach eine virtuelle Maschine, um Ubuntu 18.04 auszuführen.
Extrahieren und Lokalisieren der PyPy-Binärdatei
Die PyPy-Binärdateien werden als komprimierte Dateien bereitgestellt. Alles, was Sie tun müssen, ist die heruntergeladene Datei zu entpacken.
Innerhalb des entpackten Verzeichnisses gibt es einen Ordner namens bin
, in dem sich die ausführbare PyPy-Datei befindet. Ich verwende Python 3.6, daher heißt die Datei pypy3
. Für Python 2.7 heißt sie einfach pypy
.
PyPy über das Terminal ausführen
Unter CPython kann Python 3 direkt im Terminal mit dem Befehl python3
gestartet werden. Um PyPy auszuführen, verwendet man den Befehl:
pypy3
Falls die Eingabe von pypy3
im Terminal die Fehlermeldung „Command ‚pypy3‘ not found“ zurückgibt, kann dies daran liegen, dass sich der PyPy-Pfad nicht in der Umgebungsvariablen PATH befindet. In diesem Fall funktioniert der folgende Befehl:
./pypy3
Dabei muss sich das Terminal im bin
-Verzeichnis von PyPy befinden. Der Punkt .
steht für das aktuelle Verzeichnis, und /
wird verwendet, um auf Dateien innerhalb dieses Verzeichnisses zuzugreifen. Durch Ausführen von ./pypy3
wird Python erfolgreich gestartet, wie in der folgenden Abbildung dargestellt:
Ein Python-Skript mit PyPy ausführen
Nun kann mit Python gearbeitet und die Vorteile von PyPy genutzt werden. Zum Beispiel kann ein einfaches Python-Skript erstellt werden, das die Summe der Zahlen von 1 bis 1.000 berechnet. Der Code dazu lautet:
nums = range(1000) sum = 0 for k in nums: sum = sum + k print("Summe von 1.000 Zahlen: ", sum)
Wenn dieses Skript unter dem Namen test.py
gespeichert wird, kann es mit folgendem Befehl ausgeführt werden (vorausgesetzt, dass sich die Python-Datei im bin
-Ordner von PyPy befindet, also im selben Verzeichnis wie die ausführbare Datei pypy3
):
./pypy3 test.py
Die folgende Abbildung zeigt das Ergebnis der Ausführung des vorherigen Codes:
Ausführungszeit von PyPy vs. CPython
Um die Laufzeit von PyPy und CPython beim Summieren von 1.000 Zahlen zu vergleichen, wird der Code angepasst, um die Zeitmessung zu ermöglichen.
import time t1 = time.time() nums = range(1000) sum = 0 for k in nums: sum = sum + k print("Summe von 1.000 Zahlen: ", sum) t2 = time.time() t = t2 - t1 print("Verstrichene Zeit: ", t, " Sekunden")
Für PyPy beträgt die Laufzeit etwa 0,00045 Sekunden, während sie für CPython etwa 0,0002 Sekunden beträgt (getestet auf meinem Core i7-6500U @ 2,5 GHz). In diesem Fall ist CPython schneller als PyPy, was zu erwarten ist, da es sich nicht um eine lang laufende Aufgabe handelt.
Wird jedoch der Code so geändert, dass 1 Million Zahlen summiert werden, anstatt nur 1.000, zeigt sich der Vorteil von PyPy deutlich. In diesem Fall benötigt PyPy nur 0,00035 Sekunden, während CPython 0,1 Sekunden benötigt. Dies verdeutlicht, wie viel langsamer CPython bei der Ausführung von lang laufenden Aufgaben sein kann.
Fazit
Dieses Tutorial stellte PyPy als schnellste Python-Implementierung vor. Der größte Vorteil von PyPy ist die Just-in-Time (JIT)-Kompilierung, die es ermöglicht, kompilierten Maschinencode zwischenspeichern zu lassen, um ihn nicht erneut ausführen zu müssen.
Es wurden auch die Einschränkungen von PyPy behandelt, insbesondere dass es für reinen Python-Code gut funktioniert, aber bei C-Erweiterungen ineffizient sein kann.
Zudem haben wir gesehen, wie PyPy unter Ubuntu ausgeführt wird und die Laufzeiten von PyPy und CPython verglichen, wobei PyPy für lang laufende Prozesse erhebliche Geschwindigkeitsvorteile bietet.
In zukünftigen Artikeln werden wir weitere Vergleiche zwischen PyPy, CPython und Cython untersuchen.