Cython zu SciPy hinzufügen#
Wie auf der Cython-Website geschrieben
Cython ist ein optimierender statischer Compiler für die Python-Programmiersprache und die erweiterte Cython-Programmiersprache (basierend auf Pyrex). Es macht das Schreiben von C-Erweiterungen für Python so einfach wie Python selbst.
Wenn Ihr Code derzeit viele Schleifen in Python ausführt, kann eine Kompilierung mit Cython von Vorteil sein. Dieses Dokument ist als sehr kurze Einführung gedacht: gerade genug, um zu sehen, wie man Cython mit SciPy verwendet. Sobald Ihr Code kompiliert, können Sie mehr darüber erfahren, wie Sie ihn optimieren können, indem Sie die Cython-Dokumentation lesen.
Es gibt nur zwei Dinge, die Sie tun müssen, damit SciPy Ihren Code mit Cython kompiliert
Fügen Sie Ihren Code in eine Datei mit der Erweiterung
.pyxanstelle einer Erweiterung.pyein. Alle Dateien mit der Erweiterung.pyxwerden beim Erstellen von SciPy automatisch von Cython in.c- oder.cpp-Dateien umgewandelt.Fügen Sie die neue
.pyx-Datei zur Build-Konfigurationmeson.builddes Unterpakets hinzu, in dem Ihr Code liegt. Typischerweise sind bereits andere.pyx-Muster vorhanden (wenn nicht, schauen Sie in einem anderen Untermodul nach), so dass es ein Beispiel gibt, dem Sie folgen können, um den genauen Inhalt fürmeson.buildhinzuzufügen.
Beispiel#
scipy.optimize._linprog_rs.py enthält die Implementierung der überarbeiteten Simplexmethode für scipy.optimize.linprog. Die überarbeitete Simplexmethode führt viele elementare Zeilenoperationen an Matrizen durch und war daher ein natürlicher Kandidat für die Cythonisierung.
Beachten Sie, dass scipy/optimize/_linprog_rs.py die Klassen BGLU und LU aus ._bglu_dense genau so importiert, als wären es reguläre Python-Klassen. Aber das sind sie nicht. BGLU und LU sind Cython-Klassen, die in /scipy/optimize/_bglu_dense.pyx definiert sind. Es gibt nichts an der Art und Weise, wie sie importiert oder verwendet werden, das darauf hindeutet, dass sie in Cython geschrieben sind; die einzige Möglichkeit, wie wir bisher wissen, dass sie Cython-Klassen sind, ist, dass sie in einer Datei mit der Erweiterung .pyx definiert sind.
Selbst in /scipy/optimize/_bglu_dense.pyx ähnelt der Großteil des Codes Python. Die bemerkenswertesten Unterschiede sind das Vorhandensein von cimport, cdef und Cython-Dekoratoren. Keine davon ist streng notwendig. Ohne sie kann der reine Python-Code immer noch von Cython kompiliert werden. Die Cython-Spracherweiterungen sind *nur* Anpassungen zur Leistungsverbesserung. Diese .pyx-Datei wird beim Erstellen von SciPy automatisch von Cython in eine .c-Datei umgewandelt.
Das Einzige, was noch zu tun ist, ist die Hinzufügung der Build-Konfiguration, die in etwa so aussehen wird
_bglu_dense_c = opt_gen.process('_bglu_dense.pyx')
py3.extension_module('_bglu_dense',
_bglu_dense_c,
c_args: cython_c_args,
dependencies: np_dep,
link_args: version_link_args,
install: true,
subdir: 'scipy/optimize'
)
Wenn SciPy erstellt wird, wird _bglu_dense.pyx von cython in C-Code transpilert, und diese generierte C-Datei wird von Meson wie jeder andere C-Code in SciPy behandelt – es entsteht ein Erweiterungsmodul, aus dem wir die Klassen LU und BGLU importieren und verwenden können.
Übung#
Sehen Sie sich ein Video mit einer Durchlaufübung zu diesem Thema an: Cythonizing SciPy Code
Aktualisieren Sie Cython und erstellen Sie einen neuen Branch (z. B.
git checkout -b cython_test), in dem Sie einige experimentelle Änderungen an SciPy vornehmen könnenFügen Sie einfachen Python-Code in einer
.py-Datei im Verzeichnis/scipy/optimizehinzu, z. B./scipy/optimize/mypython.py. Zum Beispieldef myfun(): i = 1 while i < 10000000: i += 1 return i
Sehen wir uns an, wie lange diese reine Python-Schleife dauert, um die Leistung von Cython vergleichen zu können. Zum Beispiel in einer IPython-Konsole in Spyder
from scipy.optimize.mypython import myfun %timeit myfun()
Ich erhalte etwas wie
715 ms ± 10.7 ms per loop
Speichern Sie Ihre
.py-Datei als.pyx-Datei, z. B.mycython.pyx.Fügen Sie die
.pyx-Datei zuscipy/optimize/meson.buildhinzu, wie im vorherigen Abschnitt beschrieben.Bauen Sie SciPy neu. Beachten Sie, dass ein Erweiterungsmodul (eine
.so- oder.pyd-Datei) zum Verzeichnisbuild/scipy/optimize/hinzugefügt wurde.Messen Sie die Zeit, z. B. indem Sie mit
python dev.py ipythonin IPython wechseln und dannfrom scipy.optimize.mycython import myfun %timeit myfun()
Ich erhalte etwas wie
359 ms ± 6.98 ms per loop
Cython hat den reinen Python-Code um den Faktor ~2 beschleunigt.
Das ist keine große Verbesserung im großen Ganzen. Um zu verstehen, warum, ist es hilfreich, Cython eine „annotierte“ Version unseres Codes erstellen zu lassen, um Engpässe aufzuzeigen. Rufen Sie Cython in einem Terminalfenster mit Ihrer
.pyx-Datei mit dem Flag-aaufcython -a scipy/optimize/mycython.pyx
Beachten Sie, dass dadurch eine neue
.html-Datei im Verzeichnis/scipy/optimizeerstellt wird. Öffnen Sie die.html-Datei in einem beliebigen Browser.Die gelb hervorgehobenen Zeilen in der Datei deuten auf eine potenzielle Interaktion zwischen dem kompilierten Code und Python hin, was die Ausführung erheblich verlangsamt. Die Intensität der Hervorhebung gibt die geschätzte Schwere der Interaktion an. In diesem Fall kann ein Großteil der Interaktion vermieden werden, wenn wir die Variable
ials Integer definieren, damit Cython nicht die Möglichkeit berücksichtigen muss, dass es sich um ein allgemeines Python-Objekt handelt.def myfun(): cdef int i = 1 # our first line of Cython code while i < 10000000: i += 1 return i
Das Neuerstellen der annotierten
.html-Datei zeigt, dass der Großteil der Python-Interaktion verschwunden ist.Bauen Sie SciPy neu, öffnen Sie eine neue IPython-Konsole und führen Sie
%timeitaus
from scipy.optimize.mycython import myfun
%timeit myfun()
Ich erhalte etwas wie: 68.6 ns ± 1.95 ns pro Schleife. Der Cython-Code lief etwa 10 Millionen Mal schneller als der ursprüngliche Python-Code.
In diesem Fall hat der Compiler wahrscheinlich die Schleife wegoptimiert und einfach das Endergebnis zurückgegeben. Diese Art von Beschleunigung ist für echten Code nicht typisch, aber diese Übung veranschaulicht sicherlich die Leistungsfähigkeit von Cython, wenn die Alternative viele Low-Level-Operationen in Python sind.