Python-Skripte mit Cython beschleunigen
Einführung
Python könnte eine der heute beliebtesten Programmiersprachen sein, aber es ist definitiv nicht die effizienteste. Insbesondere in der Welt des maschinellen Lernens opfern Praktiker Effizienz für die Benutzerfreundlichkeit, die Python bietet.
Das bedeutet jedoch nicht, dass du die Dinge nicht auf andere Weise beschleunigen kannst. Cython ist eine einfache Möglichkeit, die Rechenzeit von Python-Skripten erheblich zu verkürzen, ohne die Funktionalität, die mit Python leicht erreicht werden kann, einzubüßen.
Dieses Tutorial wird Ihnen die Verwendung von Cython vorstellen, um Python-Skripte zu beschleunigen. Wir werden uns eine einfache, aber rechenintensive Aufgabe ansehen: das Erstellen einer for-Schleife, die durch eine Python-Liste von 1 Milliarde Zahlen iteriert und diese summiert. Da Zeit besonders wichtig ist, wenn Code auf ressourcenbeschränkten Geräten ausgeführt wird, werden wir dieses Problem in einen Kontext setzen, indem wir betrachten, wie man Python-Code in Cython auf einem Raspberry Pi (RPi) implementiert. Cython bewirkt eine signifikante Veränderung in der Geschwindigkeit der Berechnung. Denken Sie daran, es ist wie ein Faultier im Vergleich zu einem Geparden.
Voraussetzungen für die Beschleunigung von Python-Skripten mit Cython
- Grundkenntnisse in Python: Vertrautheit mit der Python-Syntax, Funktionen, Datentypen und Modulen.
- Verständnis der C/C++ Grundlagen: Grundverständnis von C- oder C++-Konzepten wie Zeigern, Datentypen und Kontrollstrukturen.
- Python-Entwicklungsumgebung: Python installiert (vorzugsweise Python 3.x), mit einem Paketmanager wie
pip. - Cython-Installation: Installieren Sie Cython mit
pip install cython. - Vertrautheit mit dem Terminal/Befehlszeile: Grundkenntnisse im Navigieren und Ausführen von Befehlen im Terminal oder der Eingabeaufforderung.
Diese Voraussetzungen werden helfen, bereit zu sein, mit der Optimierung von Python-Code mithilfe von Cython zu beginnen.
Faultierbild von IndiaToday | Gepardenbild von Pixabay
Die im Anleitung behandelten Abschnitte sind wie folgt:
- Python, CPython und Cython
- Cythonisieren von einfachem Python-Code
- Cythonisieren einer for-Schleife
- Zuweisen von C-Datentypen zu den Variablen
- Cython auf dem Raspberry Pi verwenden
Lass uns anfangen.
Python und CPython
Viele Menschen sind sich nicht bewusst, dass Programmiersprachen wie Python tatsächlich in anderen Sprachen implementiert sind. Die C-Implementierung von Python wird CPython genannt. Beachten Sie, dass es nicht Cython ist. Für weitere Informationen über die verschiedenen Implementierungen von Python können Sie diesen Beitrag lesen.
Die Standard- und beliebteste Implementierung von Python ist CPython. Es gibt einen wichtigen Vorteil bei der Verwendung davon. C ist eine kompilierte Sprache und ihr Code wird in Maschinencode umgewandelt, der direkt von der Zentraleinheit (CPU) ausgeführt wird. Jetzt fragen Sie sich vielleicht, wenn C eine kompilierte Sprache ist, bedeutet das dann, dass es Python auch ist?
Die Python-Implementierung in C (CPython) ist nicht 100 % kompiliert und auch nicht 100 % interpretiert. Sowohl Kompilierung als auch Interpretation sind im Prozess des Ausführens eines Python-Skripts beteiligt. Um dies zu verdeutlichen, sehen wir uns die Schritte zum Ausführen eines Python-Skripts an:
- Kompilierung von Quellcode mit CPython zur Erzeugung von Bytecode
- Interpretation des Bytecodes in einem CPython-Interpreter
- Die Ausführung des Outputs des CPython-Interpreters in einer CPython-virtuellen Maschine.
Die Kompilierung findet statt, wenn CPython den Quellcode (.py-Datei) kompiliert, um den CPython-Bytecode (.pyc-Datei) zu erzeugen. Der CPython-Bytecode (.pyc-Datei) wird dann mit einem CPython-Interpreter interpretiert, und die Ausgabe wird in einer CPython-virtuellen Maschine ausgeführt. Laut den oben genannten Schritten umfasst der Prozess des Ausführens eines Python-Skripts sowohl die Kompilierung als auch die Interpretation.
Der CPython-Compiler generiert den Bytecode nur einmal, aber der Interpreter wird jedes Mal aufgerufen, wenn der Code ausgeführt wird. Normalerweise dauert die Interpretation des Bytecodes viel Zeit. Wenn die Verwendung eines Interpreters die Ausführung verlangsamt, warum sollte man ihn dann überhaupt verwenden? Der große Grund ist, dass es hilft, Python plattformübergreifend zu machen. Da der Bytecode in einer CPython-virtuellen Maschine über der CPU läuft, ist er unabhängig von der Maschine, auf der er ausgeführt wird. Dadurch kann der Bytecode unverändert auf verschiedenen Maschinen ausgeführt werden.
Wenn kein Interpreter verwendet wird, wird der CPython-Compiler Maschinencode generieren, der direkt auf der CPU ausgeführt wird. Da verschiedene Plattformen unterschiedliche Anweisungen haben, wird der Code nicht plattformübergreifend sein.
Zusammenfassend lässt sich sagen, dass die Verwendung eines Compilers den Prozess beschleunigt, während ein Interpreter den Code plattformübergreifend macht. Ein Grund, warum Python langsamer als C ist, besteht darin, dass ein Interpreter verwendet wird. Denk daran, dass der Compiler nur einmal ausgeführt wird, während der Interpreter jedes Mal läuft, wenn der Code ausgeführt wird.
Python ist viel langsamer als C, aber viele Programmierer ziehen es dennoch vor, da es so viel einfacher zu bedienen ist. Python verbirgt viele Details vor dem Programmierer, was helfen kann, frustrierendes Debugging zu verhindern. Zum Beispiel, da Python eine dynamisch typisierte Sprache ist, müssen Sie den Typ jeder Variablen in Ihrem Code nicht ausdrücklich angeben – Python wird ihn automatisch ableiten. Im Gegensatz dazu müssen Sie bei statisch typisierten Sprachen (wie C, C++ oder Java) die Typen der Variablen angeben, wie unten zu sehen.
int x = 10
string s = "Hello"
Vergleichen Sie dies mit der folgenden Implementierung in Python. Dynamische Typisierung erleichtert das Programmieren, belastet jedoch die Maschine viel mehr, um den geeigneten Datentyp zu finden. Dies macht den Prozess langsamer.
x = 10
s = "Hello"
Im Allgemeinen sind "höhere" Programmiersprachen wie Python viel einfacher zu verwenden für Entwickler. Allerdings muss der Code, wenn er ausgeführt wird, in niedrigere Anweisungen umgewandelt werden. Diese Umwandlung benötigt mehr Zeit, was zugunsten der Benutzerfreundlichkeit opfernd.
Wenn Zeit ein wichtiger Faktor ist, dann müssen Sie die Anweisungen auf niedrigerer Ebene verwenden. Anstatt den Code mit Python, das die Schnittstelle ist, einzugeben, können Sie es mit CPython schreiben, das das Backend von Python ist, das in C implementiert wurde. Wenn Sie dies jedoch tun, werden Sie das Gefühl haben, in C zu programmieren, nicht in Python.
CPython ist viel komplexer. In CPython ist alles in C implementiert. Es gibt keinen Weg, der Komplexität von C beim Programmieren zu entkommen. Aus diesem Grund entscheiden sich viele Entwickler stattdessen für Cython. Aber wie unterscheidet sich Cython von CPython?
Wie sich Cython unterscheidet
Laut der Cython-Dokumentation ist Cython Python mit C-Datentypen. Eine weitere Definition aus dem Cython-Tutorial-Papier von 2009 erklärt:
Cython ist eine Programmiersprache, die auf Python basiert und zusätzliche Syntax zur Bereitstellung von statischen Typdeklarationen bietet. Dies nutzt die Vorteile von Python, während es ermöglicht, die Geschwindigkeit von C zu erreichen.
Nach den oben genannten Definitionen ist Cython eine Sprache, die es Ihnen ermöglicht, das Beste aus beiden Welten – Geschwindigkeit und Benutzerfreundlichkeit – zu haben. Sie können weiterhin normalen Code in Python schreiben, aber um die Dinge zur Laufzeit zu beschleunigen, erlaubt Cython, dass Sie einige Teile des Python-Codes durch C ersetzen. So mischen Sie beide Sprachen in einer einzigen Datei. Beachten Sie, dass Sie sich vorstellen können, dass alles in Python in Cython gültig ist, jedoch mit einigen Einschränkungen. Weitere Informationen zu den Einschränkungen finden Sie auf dieser Seite.
Die reguläre Python-Datei hat die Erweiterung .py, aber die Cython-Datei hat stattdessen die Erweiterung .pyx. Der gleiche Python-Code kann innerhalb der .pyx-Dateien geschrieben werden, aber diese ermöglichen es Ihnen auch, Cython-Code zu verwenden. Beachten Sie, dass das bloße Platzieren des Python-Codes in einer .pyx-Datei den Prozess im Vergleich zur Ausführung des Python-Codes direkt beschleunigen kann, aber nicht so sehr, wie wenn auch die Variablen-Typen deklariert werden. Daher liegt der Fokus dieses Tutorials nicht nur darauf, den Python-Code innerhalb der .pyx-Datei zu schreiben, sondern auch darauf, Änderungen vorzunehmen, die das Ausführen schneller machen. Dadurch fügen wir der Programmierung ein wenig Schwierigkeiten hinzu, aber wir sparen viel Zeit dabei. Wenn Sie Erfahrung mit der C-Programmierung haben, wird es für Sie noch einfacher sein.
Cythonisieren von einfachem Python-Code
Um Ihr Python in Cython umzuwandeln, müssen Sie zunächst eine Datei mit der .pyx-Erweiterung anstelle der .py-Erweiterung erstellen. In dieser Datei können Sie damit beginnen, regulären Python-Code zu schreiben (beachten Sie, dass es einige Einschränkungen im Python-Code gibt, die von Cython akzeptiert werden, wie in den Cython-Dokumenten erläutert).
Bevor Sie fortfahren, stellen Sie sicher, dass Cython installiert ist. Sie können dies mit dem folgenden Befehl tun.
pip install cython
Um die .pyd/.so-Datei zu generieren, müssen wir zuerst die Cython-Datei erstellen. Die .pyd/.so-Datei repräsentiert das Modul, das später importiert werden soll. Um die Cython-Datei zu erstellen, wird eine setup.py-Datei verwendet. Erstellen Sie diese Datei und fügen Sie den untenstehenden Code ein. Wir verwenden die Funktion distutils.core.setup(), um die Funktion Cython.Build.cythonize() aufzurufen, die die .pyx-Datei cythonisiert. Diese Funktion akzeptiert den Pfad der Datei, die Sie cythonisieren möchten. Hier gehe ich davon aus, dass sich die setup.py-Datei am selben Ort 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 den Befehl unten im Eingabeaufforderung ein. Es wird erwartet, dass das aktuelle Verzeichnis der Eingabeaufforderung dasselbe ist wie das Verzeichnis der setup.py-Datei.
python setup.py build_ext --inplace
Nachdem dieser Befehl abgeschlossen ist, werden zwei Dateien neben der .pyx-Datei abgelegt. Die erste hat die .c-Erweiterung und die andere Datei hat die Erweiterung .pyd (oder ähnlich, je nach verwendetem Betriebssystem). Um die generierte Datei zu verwenden, importiere einfach das test_cython-Modul und die "Hello Cython"-Nachricht wird direkt angezeigt, wie Sie unten sehen.
Wir haben jetzt erfolgreich den Python-Code cythonisiert. Der nächste Abschnitt behandelt das Cythonisieren einer .pyx-Datei, in der eine Schleife erstellt wird.
Cythonisierung einer “for” Schleife
Jetzt optimieren wir unsere oben erwähnte Aufgabe: eine for-Schleife, die durch 1 Million Zahlen iteriert und diese summiert. Lassen Sie uns zunächst die Effizienz nur der Iterationen der Schleife betrachten. Das Modul time wird importiert, um zu schätzen, wie lange es dauert, die Ausführung durchzuführen.
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 mittlere Zeit für 3 Durchläufe 0,0281 Sekunden. Der Code läuft auf einer Maschine mit Core i7-6500U CPU @ 2,5 GHz und 16 GB DDR3 RAM.
Vergleichen Sie dies mit der Zeit, die benötigt wird, um in einer normalen Python-Datei auszuführen, deren Durchschnitt 0,0411 Sekunden beträgt. Das bedeutet, dass Cython 1,46 Mal schneller ist als Python nur für die Iterationen, obwohl wir die for-Schleife nicht ändern müssen, um sie mit C-Geschwindigkeit auszuführen.
Jetzt fügen wir die Summierungsaufgabe hinzu. Wir werden dafür die Funktion range() verwenden.
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, der 499999500000 beträgt. In Python dauert dies im Durchschnitt 0,1183 Sekunden (über drei Versuche). In Cython ist es 1,35 Mal schneller, mit einem Durchschnitt von 0,0875 Sekunden.
Lass uns ein weiteres Beispiel ansehen, in dem die Schleife durch 1 Milliarde Zahlen ab 0 iteriert.
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 benötigte fast 85 Sekunden (1,4 Minuten), während das Python-Skript fast 115 Sekunden (1,9 Minuten) benötigte. In beiden Fällen ist es einfach zu viel Zeit. Was ist der Vorteil der Verwendung von Cython, wenn es bei einer so trivialen Aufgabe mehr als eine Minute dauert? Beachten Sie, dass dies unser Fehler ist, nicht der von Cython.
Wie bereits zuvor besprochen, ist es eine Verbesserung, den Python-Code innerhalb des Cython .pyx-Skripts zu schreiben, aber es führt nicht zu einer großen Reduzierung der Ausführungszeit. Wir müssen Änderungen am Python-Code im Cython-Skript vornehmen. Der erste Punkt, auf den wir uns konzentrieren sollten, ist die explizite Definition der Datentypen der verwendeten Variablen.
Zuweisen von C-Datentypen zu Variablen
Laut dem vorherigen Code werden 5 Variablen verwendet: total, k, t1, t2 und t. Alle diese Variablen haben ihre Datentypen, die durch den Code implizit abgeleitet werden, wodurch mehr Zeit benötigt wird. Um die Zeit, die für die Ableitung ihrer Datentypen aufgewendet wird, zu sparen, lassen Sie uns ihre Datentypen stattdessen aus der Programmiersprache C zuweisen.
Der Typ der total-Variablen ist unsigned long long int. Es ist eine Ganzzahl, weil die Summe aller Zahlen eine Ganzzahl ist, und sie ist unsigned, weil die Summe positiv sein wird. Aber warum ist es long long? Weil die Summe aller Zahlen sehr groß ist, wird long long hinzugefügt, um die Variablen Größe auf die maximal mögliche Größe zu erhöhen.
Der Typ, der für die Variable k definiert ist, ist int, und der Typ float wird für die verbleibenden drei Variablen t1, t2 und t 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 Präzision, die im letzten Druckbefehl definiert ist, auf 100 gesetzt ist und all diese Zahlen null sind (siehe die nächste Abbildung). Das ist das, was wir von der Verwendung von Cython erwarten können. Während Python mehr als 1,9 Minuten benötigt, benötigt Cython überhaupt keine Zeit. Ich kann nicht einmal sagen, dass die Geschwindigkeit 1000 oder 100000 Mal schneller als Python ist; ich habe verschiedene Präzisionen für die ausgegebene Zeit ausprobiert und trotzdem erscheint keine Zahl.
Beachten Sie, dass Sie auch eine Ganzzahlvariable erstellen können, um den Wert zu speichern, der an die Funktion range() übergeben wird. Dies wird die Leistung noch weiter steigern. Der neue Code ist unten aufgeführt, wo der Wert in der Ganzzahlvariable maxval gespeichert wird.
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)
Jetzt, da wir gesehen haben, wie man die Leistung der Python-Skripte mit Cython beschleunigen kann, lassen Sie uns dies auf den Raspberry Pi (RPi) anwenden.
Zugriff auf Raspberry Pi vom PC
Wenn dies das erste Mal ist, dass Sie Ihren Raspberry Pi verwenden, müssen sowohl Ihr PC als auch der RPi über ein Netzwerk verbunden werden. Sie können dies tun, indem Sie beide an einen Switch anschließen, in dem der DHCP (Dynamic Host Configuration Protocol) aktiv ist, um automatisch IP-Adressen zuzuweisen. Nach erfolgreicher Netzwerkbildung können Sie auf den RPi basierend auf der ihm zugewiesenen IPv4-Adresse zugreifen. Wie erfahren Sie, welche IPv4-Adresse Ihrem RPi zugewiesen wurde? Keine Sorge, Sie können einfach ein IP-Scanner-Tool verwenden. In diesem Tutorial werde ich eine kostenlose Anwendung namens Advanced IP Scanner verwenden.
Die Benutzeroberfläche der Anwendung ist wie unten zu sehen. Sie akzeptiert eine Reihe von IPv4-Adressen zur Suche und gibt die Informationen für aktive Geräte zurück.
Sie müssen den Bereich der IPv4-Adressen in Ihrem lokalen Netzwerk eingeben. Wenn Sie den Bereich nicht kennen, führen Sie einfach den Befehl ipconfig in Windows (oder ifconfig in Linux) aus, um die IPv4-Adresse Ihres PCs zu erfahren (wie im Bild unten dargestellt). In meinem Fall ist die der Wi-Fi-Adapter meines PCs zugewiesene IPv4-Adresse 192.168.43.177 und die Subnetzmaske ist 255.255.255.0. Das bedeutet, dass der Bereich der IPv4-Adressen im Netzwerk von 192.168.43.1 bis 192.168.43.255 reicht. Laut Abbildung ist die IPv4-Adresse 192.168.43.1 dem Gateway zugewiesen. Beachten Sie, dass die letzte IPv4-Adresse im Bereich, 192.168.43.255, für Broadcast-Nachrichten reserviert ist. Daher sollte der zu durchsuchende Bereich von 192.168.43.2 bis 192.168.43.254 beginnen.
Laut dem Ergebnis des Scans, das in der nächsten Abbildung gezeigt wird, ist die IPv4-Adresse, die dem RPi zugewiesen wurde, 192.168.43.63. Diese IPv4-Adresse kann verwendet werden, um eine sichere Shell (SSH)-Sitzung zu erstellen.
Um die SSH-Sitzung einzurichten, werde ich eine kostenlose Software namens MobaXterm verwenden. Die Benutzeroberfläche der Anwendung sieht wie folgt aus.
Um eine SSH-Sitzung zu erstellen, klicken Sie einfach auf die Session-Schaltfläche in der oberen linken Ecke. Ein neues Fenster erscheint wie unten gezeigt.
Klicken Sie in diesem Fenster auf die Schaltfläche SSH in der oberen linken Ecke, um das unten gezeigte Fenster zu öffnen. Geben Sie einfach die IPv4-Adresse des RPi und den Benutzernamen (der standardmäßig pi ist) ein und klicken Sie dann auf OK, um die Sitzung zu starten.
Nachdem Sie auf die Schaltfläche OK geklickt haben, erscheint ein neues Fenster, das nach dem Passwort fragt. Das Standardpasswort lautet raspberrypi. Nach dem Einloggen erscheint das nächste Fenster. Der Bereich auf der linken Seite hilft, die Verzeichnisse des RPi einfach zu navigieren. Es gibt auch eine Eingabeaufforderung zum Eingeben von Befehlen.
Cython mit Raspberry Pi verwenden
Erstellen Sie eine neue Datei und setzen Sie ihre Erweiterung auf .pyx, um den Code des letzten Beispiels zu schreiben. Im Menü auf der linken Seite gibt es Optionen zum Erstellen neuer Dateien und Verzeichnisse. Sie können das Symbol für neue Dateien verwenden, um die Dinge noch einfacher zu machen, wie in der folgenden Abbildung gezeigt. Ich habe eine Datei namens test_cython.pyx im Stammverzeichnis des RPi erstellt.
Doppelklicken Sie einfach auf die Datei, um sie zu öffnen, fügen Sie den Code ein und speichern Sie ihn. Danach können wir die setup.py Datei erstellen, die genau die gleiche ist wie die, die wir zuvor besprochen haben. Als nächstes müssen wir den folgenden Befehl zum Erstellen des Cython-Skripts ausführen.
python3 setup.py build_ext --inplace
Nachdem dieser Befehl erfolgreich abgeschlossen wurde, finden Sie die Ausgabedateien in dem linken Bereich gemäß der nächsten Abbildung. Beachten Sie, dass die Erweiterung des zu importierenden Moduls jetzt .so ist, da wir Windows nicht mehr verwenden.
Lass uns jetzt Python aktivieren und das Modul importieren, wie unten gezeigt. Die gleichen Ergebnisse, die auf dem PC erzielt wurden, werden auch hier erzielt; die verbrauchte Zeit ist im Wesentlichen null.
Schlussfolgerung
Dieses Tutorial behandelte, wie man Cython verwendet, um die Rechenzeit der Ausführung von Python-Skripten zu reduzieren. Wir betrachteten das Beispiel einer for-Schleife, um alle Elemente in einer Python-Liste mit 1 Milliarde Zahlen zu summieren, und verglichen die Ausführungszeit mit und ohne Deklaration der Variablentypen. Während dies in reinem Python fast zwei Minuten dauert, benötigt es mit in Cython deklarierten statischen Variablen praktisch keine Zeit zur Ausführung.
Im nächsten Tutorial werden wir diese Python-Liste durch ein NumPy-Array ersetzen und sehen, wie wir die Verarbeitung von NumPy-Arrays mit Cython optimieren können. Dann werden wir uns ansehen, wie man fortgeschrittenere Python-Skripte, wie genetische Algorithmen, cythonisieren kann. Dies sind großartige Möglichkeiten, um die Effizienz Ihrer Machine-Learning-Projekte einfach zu steigern.















