Einführung
Python mag zwar eine der beliebtesten Programmiersprachen heutzutage sein, aber sicherlich nicht die effizienteste. Gerade im Bereich des maschinellen Lernens opfern Anwender die Effizienz zugunsten der Benutzerfreundlichkeit von Python.
Das heißt aber nicht, dass man die Sache nicht auch auf anderem Wege beschleunigen kann. Cython ist eine einfache Möglichkeit, die Rechenzeit von Python-Skripten zu reduzieren, ohne dabei auf die Funktionalität zu verzichten, die sich mit Python problemlos realisieren lässt.
Dieses Tutorial führt Sie in die Verwendung von Cython zur Beschleunigung von Python-Skripten ein. Wir widmen uns einer einfachen, aber rechenintensiven Aufgabe: der Erstellung einer for-Schleife, die eine Python-Liste mit einer Milliarde Zahlen durchläuft und diese summiert. Da Zeit bei der Ausführung von Code auf ressourcenbeschränkten Geräten entscheidend ist, untersuchen wir dieses Problem anhand der Implementierung von Python-Code in Cython auf einem Raspberry Pi (RPi). Cython bewirkt einen signifikanten Unterschied in der Rechengeschwindigkeit; vergleichbar mit dem Unterschied zwischen Lazy Loading und Yozo.
Voraussetzungen für die Optimierung von Python-Skripten mit Cython
- Grundkenntnisse in Python: Vertrautheit mit der Python-Syntax, Funktionen, Datentypen und Modulen.
- Grundlegende C/C++-Konzepte verstehen: Vertrautheit mit grundlegenden C- oder C++-Konzepten wie Zeigern, Datentypen und Kontrollstrukturen.
- Python-Entwicklungsumgebung: Installieren Sie Python (vorzugsweise Python 3.x) mit einem Paketmanager wie pip.
- Cython installieren: Installieren Sie Cython mit dem Befehl pip install cython.
- Terminal-/Befehlszeilenkenntnisse: Grundlegende Fähigkeit zur Navigation und Ausführung von Befehlen in einem Terminal oder einer Befehlszeile.
Diese Voraussetzungen helfen Ihnen dabei, sich auf die Optimierung von Python-Code mit Cython vorzubereiten.
Python und CPython
Vielen ist nicht bewusst, dass Programmiersprachen wie Python auch in anderen Sprachen implementiert sind. Die C-Implementierung von Python heißt beispielsweise CPython. Diese ist nicht mit Cython zu verwechseln. Weitere Informationen zu den verschiedenen Python-Implementierungen finden Sie in diesem Artikel.
Die Standard- und beliebteste Implementierung von Python ist C-Python. Die Verwendung von C bietet einen wichtigen Vorteil: C ist eine kompilierte Sprache, deren Code in Maschinencode übersetzt und direkt von der Zentraleinheit (CPU) ausgeführt wird. Nun könnte man sich fragen: Wenn C eine kompilierte Sprache ist, bedeutet das, dass Python dasselbe ist?
Die Python-Implementierung in C (CPython) 100% wird weder kompiliert noch interpretiert. Tatsächlich finden Kompilierung und Interpretation erst während der Ausführung eines Python-Skripts statt. Um dies zu verdeutlichen, betrachten wir die einzelnen Schritte der Ausführung eines Python-Skripts:
- Kompilieren von Quellcode mit CPython zur Erzeugung von Bytecode
- Bytecode-Interpretation durch den CPython-Interpreter
- Die Ausgabe des CPython-Interpreters in der virtuellen CPython-Maschine ausführen
Der Kompilierungsprozess findet statt, wenn CPython den Quellcode (die .py-Datei) kompiliert und CPython-Bytecode (die .pyc-Datei) erzeugt. Dieser Bytecode wird anschließend vom CPython-Interpreter interpretiert und die Ausgabe in der CPython-VM ausgeführt. Wie die obigen Schritte zeigen, umfasst die Ausführung eines Python-Skripts sowohl die Kompilierung als auch die Interpretation.
Der CPython-Compiler erzeugt Bytecode nur einmal, der Interpreter wird jedoch bei jeder Codeausführung aufgerufen. Die Interpretation von Bytecode ist in der Regel zeitaufwendig. Wenn die Verwendung eines Interpreters die Ausführung verlangsamt, warum sollte man ihn dann überhaupt einsetzen? Der Hauptgrund ist, dass der Interpreter Python auf verschiedenen Betriebssystemen nutzbar macht. Da der Bytecode unabhängig vom jeweiligen Rechner in der CPython-VM, die auf der CPU läuft, ausgeführt wird, kann er ohne Änderungen auf unterschiedlichen Rechnern ausgeführt werden.
Wird kein Interpreter verwendet, erzeugt der CPython-Compiler Maschinencode, der direkt auf der CPU ausgeführt wird. Da verschiedene Plattformen unterschiedliche Befehle verwenden, ist der Code nicht auf allen Plattformen lauffähig.
Kurz gesagt: Ein Compiler beschleunigt den Prozess, aber der Interpreter macht den Code plattformübergreifend. Einer der Gründe, warum Python langsamer als C ist, liegt also in der Verwendung des Interpreters. Der Compiler wird nur einmal ausgeführt, der Interpreter hingegen bei jeder Codeausführung.
Python ist deutlich langsamer als C, wird aber von vielen Programmierern dennoch bevorzugt, da es wesentlich einfacher zu bedienen ist. Python verbirgt viele Details vor dem Programmierer, was frustrierendes Debuggen verhindern kann. Da Python beispielsweise eine dynamisch typisierte Sprache ist, muss der Typ jeder Variablen im Code nicht explizit angegeben werden – Python leitet ihn automatisch ab. Im Gegensatz dazu müssen in statisch typisierten Sprachen (wie C, C++ oder Java) die Variablentypen explizit angegeben werden, wie unten gezeigt.
int x = 10
string s = "Hello"Vergleichen Sie mit der folgenden Implementierung in Python:
Dynamische Typisierung vereinfacht zwar das Programmieren, belastet aber die Maschine zusätzlich mit der Suche nach dem richtigen Datentyp. Dies verlangsamt den Ausführungsprozess.
x = 10
s = "Hello"Im Allgemeinen sind “höhere” Programmiersprachen wie Python für Entwickler deutlich leichter verständlich. Bei der Ausführung des Codes muss dieser jedoch in maschinennahe Anweisungen übersetzt werden. Diese Übersetzung ist zeitaufwendiger und wird zugunsten der einfacheren Handhabung in Kauf genommen.
Wenn Zeit ein Faktor ist, sollten Sie Low-Level-Befehle verwenden. Anstatt also Code in Python zu schreiben, der die Benutzeroberfläche bildet, können Sie ihn mit CPython schreiben, das im Hintergrund Python nutzt und in C implementiert ist. Allerdings werden Sie sich dabei fühlen, als würden Sie in C programmieren, nicht in Python.
CPython ist deutlich komplexer. In CPython ist alles in C implementiert. Die Komplexität von C lässt sich beim Programmieren nicht umgehen. Deshalb verwenden viele Entwickler stattdessen Cython. Doch worin genau unterscheidet sich Cython von CPython?
Worin unterscheidet sich Cython?
Wie oben beschrieben, vereint Cython die Vorteile von Geschwindigkeit und Benutzerfreundlichkeit. Sie können weiterhin regulären Python-Code schreiben, aber um die Ausführungszeit zu beschleunigen, ermöglicht Cython das Ersetzen von Teilen des Python-Codes durch C-Code. So kombinieren Sie letztendlich beide Sprachen in einer einzigen Datei. Beachten Sie, dass im Prinzip alles, was in Python möglich ist, auch in Cython gültig ist, jedoch mit einigen Einschränkungen.
Eine reguläre Python-Datei hat die Endung .py, eine Cython-Datei hingegen die Endung .pyx. Derselbe Python-Code kann in .pyx-Dateien geschrieben werden, diese ermöglichen aber auch die Verwendung von Cython-Code. Beachten Sie, dass das Einfügen von Python-Code in eine .pyx-Datei die Ausführung zwar beschleunigen kann, aber nicht so schnell ist wie die Deklaration der Variablentypen. Daher konzentriert sich dieses Tutorial nicht nur auf das Schreiben von Python-Code in einer .pyx-Datei, sondern auch auf Optimierungen, die die Ausführungsgeschwindigkeit erhöhen. Dies macht die Programmierung zwar etwas komplexer, spart aber viel Zeit. Wenn Sie bereits Erfahrung mit der C-Programmierung haben, wird Ihnen dies leichter fallen.
Citronisierung von einfachem Python-Code
Um Python-Code in Cython zu konvertieren, müssen Sie zuerst eine Datei mit der Erweiterung erstellen. .pyx Erstellen statt Erweiterung .py. Innerhalb dieser Datei können Sie mit dem Schreiben von regulärem Python-Code beginnen (beachten Sie, dass es einige Einschränkungen hinsichtlich des von Cython akzeptierten Codes gibt, die in der Cython-Dokumentation erläutert werden).
Bevor Sie fortfahren, stellen Sie sicher, dass Cython installiert ist. Dies können Sie mit dem folgenden Befehl tun.
pip install cython
Um die .pyd-/.so-Datei zu generieren, muss zunächst die Cython-Datei erstellt werden. Die .pyd-/.so-Datei repräsentiert das Modul, das später importiert wird. Die Cython-Datei wird mithilfe einer setup.py-Datei erstellt. Erstellen Sie diese Datei und fügen Sie den folgenden Code ein. Wir verwenden die Funktion distutils.core.setup(), um die Funktion Cython.Build.cythonize() aufzurufen, welche die .pyx-Datei cyanogenisiert. Diese Funktion erwartet den Pfad zur zu cyanogenisierenden Datei. Hier wird davon ausgegangen, dass sich die setup.py-Datei im selben Verzeichnis wie die test_cython.pyx-Datei befindet.
import distutils.core
import Cython.Build
distutils.core.setup(
ext_modules = Cython.Build.cythonize("test_cython.pyx"))Um die Cython-Datei zu erstellen, geben Sie folgenden Befehl in der Kommandozeile ein. Das aktuelle Verzeichnis der Kommandozeile muss mit dem Verzeichnis der Datei setup.py übereinstimmen.
python setup.py build_ext --inplace
Nach Abschluss dieses Befehls werden neben der .pyx-Datei zwei weitere Dateien erstellt. Die erste Datei hat die Endung .c, die zweite die Endung .pyd (oder ähnlich, abhängig vom verwendeten Betriebssystem). Um die generierte Datei zu verwenden, importieren Sie einfach das Modul test_cython. Daraufhin wird die Meldung “Hello Cython” direkt angezeigt, wie Sie unten sehen können.
Wir haben den Python-Code nun erfolgreich mit Cyton kompiliert. Im nächsten Abschnitt geht es um die Kompilierung einer .pyx-Datei mit Cyton, die eine Schleife enthält.
Zytisierung einer "for"-Schleife“
Optimieren wir nun unsere vorherige Aufgabe: eine for-Schleife, die eine Million Zahlen durchläuft und summiert. Beginnen wir mit der Untersuchung der Effizienz der Schleife selbst. Das Modul `time` dient dazu, die Ausführungszeit zu schätzen.
import time
t1 = time.time()
for k in range(1000000):
pass
t2 = time.time()
t = t2-t1
print("%.20f" % t)In einer .pyx-Datei beträgt die durchschnittliche Ausführungszeit für drei Durchläufe 0,0281 Sekunden. Der Code läuft auf einem Rechner mit einem Core i7-6500U Prozessor (2,5 GHz) und 16 GB DDR3-RAM.
Vergleichen wir dies mit der Ausführungszeit einer typischen Python-Datei, die durchschnittlich 0,0411 Sekunden beträgt. Das bedeutet, dass Cython bei Iterationen nur 1,46-mal schneller ist als Python, selbst ohne die for-Schleife an die Geschwindigkeit von C anpassen zu müssen.
Nun fügen wir die Addition hinzu. Dazu verwenden wir die Funktion range().
import time
t1 = time.time()
total = 0
for k in range(1000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.100f" % t)Beachten Sie, dass beide Skripte denselben Wert zurückgeben, nämlich 499999500000. In Python dauert die Ausführung im Durchschnitt 0,1183 Sekunden (über drei Tests). In Cython ist sie 1,35-mal schneller und benötigt durchschnittlich 0,0875 Sekunden.
Schauen wir uns nun ein anderes Beispiel an, bei dem die Schleife bei 0 beginnt und 1 Milliarde Zahlen durchläuft.
import time
t1 = time.time()
total = 0
for k in range(1000000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.20f" % t)Das Cython-Skript war nach etwa 85 Sekunden (1,4 Minuten) fertig, das Python-Skript hingegen nach etwa 115 Sekunden (1,9 Minuten). In beiden Fällen dauert es viel zu lange. Welchen Sinn hat es, Cython zu verwenden, wenn selbst eine so einfache Aufgabe über eine Minute dauert? Wohlgemerkt, das ist unser Fehler, nicht der von Cython.
Wie bereits erwähnt, ist das Schreiben von Python-Code innerhalb eines Cython-Skripts (.pyx) zwar eine Verbesserung, führt aber nicht zu einer signifikanten Laufzeitverbesserung. Daher müssen wir einige Änderungen am Python-Code innerhalb des Cython-Skripts vornehmen. Zunächst müssen wir die Datentypen der verwendeten Variablen explizit deklarieren.
Zuweisung von C-Datentypen zu Variablen
Im vorherigen Code werden fünf Variablen verwendet: total, k, t1, t2 und t. Der Datentyp dieser Variablen wird implizit vom Code abgeleitet, was Zeit kostet. Um Zeit bei der Datentypableitung zu sparen, weisen wir ihnen die Datentypen aus der Programmiersprache C zu.
Der Datentyp der Variable `total` ist `unsigned long long int`. Sie ist vom Typ `int`, da die Summe aller Zahlen eine ganze Zahl ist, und vom Typ `unsigned`, da die Summe immer positiv ist. Aber warum `long long`? Weil die Summe aller Zahlen sehr groß ist, wird `long long` verwendet, um die Variable so groß wie möglich zu machen.
Der Variable k wurde der Datentyp int zugewiesen, den übrigen drei Variablen t1, t2 und t wurde der Datentyp float zugewiesen.
import time
cdef unsigned long long int total
cdef int k
cdef float t1, t2, t
t1 = time.time()
for k in range(1000000000):
total = total + k
print "Total =", total
t2 = time.time()
t = t2-t1
print("%.100f" % t)Beachten Sie, dass die in der letzten print-Anweisung definierte Genauigkeit auf 100 gesetzt ist und alle Zahlen Null sind (siehe nächstes Bild). Genau das können wir von Cython erwarten. Während Python mehr als 1,9 Minuten benötigt, ist Cython in kürzester Zeit fertig. Ich würde nicht einmal behaupten, dass es 1000- oder 100000-mal schneller ist als Python; ich habe verschiedene Genauigkeiten für die Ausgabe der Zeit ausprobiert, und es wird trotzdem keine Zahl angezeigt.
Beachten Sie, dass Sie auch eine Integer-Variable erstellen können, um den an die Funktion `range()` übergebenen Wert zu speichern. Dies verbessert die Performance zusätzlich. Der neue Code ist unten aufgeführt; der Wert wird in der Integer-Variable `maxval` gespeichert.
import time
cdef unsigned long long int maxval
cdef unsigned long long int total
cdef int k
cdef float t1, t2, t
maxval=1000000000
t1=time.time()
for k in range(maxval):
total = total + k
print "Total =", total
t2=time.time()
t = t2-t1
print("%.100f" % t)Nachdem wir gesehen haben, wie man die Leistung von Python-Skripten mithilfe von Cython steigern kann, wenden wir dies nun auf den Raspberry Pi (RPi) an.
Raspberry Pi-Zugriff von einem PC aus
Wenn Sie Ihren Raspberry Pi zum ersten Mal verwenden, müssen Sie sowohl Ihren PC als auch Ihren RPi mit einem Netzwerk verbinden. Verbinden Sie dazu beide Geräte mit einem Switch, auf dem DHCP (Dynamic Host Configuration Protocol) aktiviert ist, um IP-Adressen automatisch zuzuweisen. Sobald das Netzwerk erfolgreich eingerichtet ist, können Sie über die zugewiesene IPv4-Adresse auf Ihren RPi zugreifen. Wie finden Sie heraus, welche IPv4-Adresse Ihr RPi hat? Keine Sorge, Sie können einfach ein IP-Scanner-Tool verwenden. In diesem Tutorial verwende ich die kostenlose App „Advanced IP Scanner“.
Die Benutzeroberfläche dieser Anwendung sieht wie folgt aus. Diese Anwendung akzeptiert einen Bereich von IPv4-Adressen zur Suche und gibt Informationen über aktive Geräte zurück.
Sie müssen den IPv4-Adressbereich Ihres lokalen Netzwerks eingeben. Falls Sie diesen Bereich nicht kennen, führen Sie unter Windows den Befehl `ipconfig` (oder unter Linux `ifconfig`) aus, um die IPv4-Adresse Ihres Computers zu ermitteln (siehe Abbildung unten). In meinem Fall lautet die IPv4-Adresse des WLAN-Adapters meines Computers 192.168.43.177 und die Subnetzmaske 255.255.255.0. Das bedeutet, dass der IPv4-Adressbereich im Netzwerk von 192.168.43.1 bis 192.168.43.255 reicht. Wie in der Abbildung dargestellt, ist die IPv4-Adresse 192.168.43.1 dem Gateway zugewiesen. Beachten Sie, dass die letzte IPv4-Adresse in diesem Bereich, 192.168.43.255, für Broadcast-Nachrichten reserviert ist. Der Suchbereich beginnt also bei 192.168.43.2 und endet bei 192.168.43.254.
Dem Scan-Ergebnis in der nächsten Abbildung zufolge lautet die dem RPi zugewiesene IPv4-Adresse 192.168.43.63. Diese IPv4-Adresse kann verwendet werden, um eine Secure Shell (SSH)-Sitzung herzustellen.
Um eine SSH-Sitzung herzustellen, verwende ich die kostenlose Software MobaXterm. Die Benutzeroberfläche dieses Programms sieht wie folgt aus.
Um eine SSH-Sitzung zu erstellen, klicken Sie einfach auf die Schaltfläche „Sitzung“ in der oberen linken Ecke. Ein neues Fenster wird wie unten dargestellt angezeigt.
Klicken Sie in diesem Fenster auf die Schaltfläche „SSH“ oben links, um das unten abgebildete Fenster zu öffnen. Geben Sie einfach die IPv4-Adresse und den Benutzernamen des Raspberry Pi (standardmäßig „pi“) ein und klicken Sie anschließend auf „OK“, um die Sitzung zu starten.
Nach dem Klicken auf „OK“ öffnet sich ein neues Fenster, in dem Sie das Passwort eingeben müssen. Das Standardpasswort lautet „raspberrypi“. Nach der Anmeldung erscheint das folgende Fenster. Im linken Bereich können Sie bequem durch die Verzeichnisse Ihres Raspberry Pi navigieren. Außerdem gibt es eine Kommandozeile zur Eingabe von Befehlen.
Cython mit Raspberry Pi verwenden
Erstellen Sie eine neue Datei und ändern Sie deren Dateiendung in .pyx, um den Code aus dem vorherigen Beispiel einzufügen. In der linken Leiste finden Sie Optionen zum Erstellen neuer Dateien und Verzeichnisse. Das Symbol für eine neue Datei erleichtert dies, wie in der Abbildung unten gezeigt. Ich habe im Stammverzeichnis des Raspberry Pi eine Datei namens test_cython.pyx erstellt.
Doppelklicken Sie auf die Datei, um sie zu öffnen, fügen Sie den Code ein und speichern Sie sie. Als Nächstes erstellen wir die Datei setup.py, die genau wie zuvor besprochen aufgebaut sein wird. Danach führen wir den folgenden Befehl aus, um das Cython-Skript zu kompilieren.
python3 setup.py build_ext --inplace
Nach erfolgreicher Ausführung dieses Befehls finden Sie die Ausgabedateien im linken Bereich, wie in der folgenden Abbildung dargestellt. Beachten Sie, dass die Dateiendung des zu importierenden Moduls nun .so lautet, da wir nicht mehr Windows verwenden.
Aktivieren wir nun Python und importieren das Modul, wie unten gezeigt. Die Ergebnisse sind dieselben wie auf einem PC; der Zeitaufwand ist praktisch null.
Ergebnis
Dieses Tutorial befasste sich mit der Verwendung von Cython zur Reduzierung der Ausführungszeit von Python-Skripten. Wir zeigen ein Beispiel für die Verwendung einer Schleife. für Wir haben die Addition aller Elemente einer Python-Liste mit einer Milliarde Zahlen untersucht und die Ausführungszeit mit und ohne Deklaration der Variablen verglichen. Während dieser Vorgang in reinem Python fast zwei Minuten dauert, läuft er mit Cython und der Deklaration statischer Variablen praktisch in kürzester Zeit ab.






















