Cython-Tutorial: So beschleunigen Sie Python
Python ist eine leistungsstarke Programmiersprache, die leicht zu erlernen und zu bearbeiten ist, aber nicht immer am schnellsten ausgeführt werden kann - insbesondere, wenn Sie sich mit Mathematik oder Statistik beschäftigen. Bibliotheken von Drittanbietern wie NumPy, die C-Bibliotheken umschließen, können die Leistung einiger Vorgänge erheblich verbessern. Manchmal benötigen Sie jedoch nur die Geschwindigkeit und Leistung von C direkt in Python.
Cython wurde entwickelt, um das Schreiben von C-Erweiterungen für Python zu vereinfachen und die Umwandlung von vorhandenem Python-Code in C zu ermöglichen. Darüber hinaus ermöglicht Cython den Versand des optimierten Codes mit einer Python-Anwendung ohne externe Abhängigkeiten.
In diesem Tutorial werden die Schritte beschrieben, die erforderlich sind, um vorhandenen Python-Code in Cython umzuwandeln und in einer Produktionsanwendung zu verwenden.
Zugehöriges Video: Verwenden von Cython zur Beschleunigung von Python
Ein Cython-Beispiel
Beginnen wir mit einem einfachen Beispiel aus der Dokumentation von Cython, einer nicht sehr effizienten Implementierung einer integralen Funktion:
def f (x):return x ** 2-x
def integriere_f (a, b, N):
s = 0
dx = (ba) / N.
für i im Bereich (N):
s + = f (a + i * dx)
return s * dx
Der Code ist leicht zu lesen und zu verstehen, läuft aber langsam. Dies liegt daran, dass Python ständig zwischen seinen eigenen Objekttypen und den numerischen Rohtypen der Maschine hin und her konvertieren muss.
Betrachten Sie nun die Cython-Version desselben Codes, wobei die Ergänzungen von Cython unterstrichen sind:
cdef f (doppeltes x):return x ** 2-x
def integriere_f (double a, double b, int N):
cdef int i
cdef double s, x, dx
s = 0
dx = (ba) / N.
für i im Bereich (N):
s + = f (a + i * dx)
return s * dx
Mit diesen Ergänzungen können wir Variablentypen im gesamten Code explizit deklarieren, sodass der Cython-Compiler diese „dekorierten“ Ergänzungen in C übersetzen kann.
Zugehöriges Video: Wie Python die Programmierung erleichtert
Python ist perfekt für die IT geeignet und vereinfacht viele Arten von Arbeiten, von der Systemautomatisierung bis hin zur Arbeit in hochmodernen Bereichen wie maschinellem Lernen.
Cython-Syntax
Die zum Dekorieren von Cython-Code verwendeten Schlüsselwörter sind in der herkömmlichen Python-Syntax nicht enthalten. Sie wurden speziell für Cython entwickelt, sodass mit ihnen dekorierter Code nicht als herkömmliches Python-Programm ausgeführt werden kann.
Dies sind die häufigsten Elemente der Cython-Syntax:
Variablentypen
Einige der Variablentypen verwendet in Cython sind Echos von Python eigenen Arten, wie int
, float
und long
. Andere Cython-Variablentypen finden sich ebenfalls in C, wie char
oder struct
, ebenso wie Deklarationen wie unsigned long
. Andere sind für Cython einzigartig bint
, beispielsweise eine C-Level-Darstellung von Python- True/False
Werten.
Die cdef
und cpdef
Funktionstypen
Das cdef
Schlüsselwort gibt die Verwendung eines Cython- oder C-Typs an. Es wird auch verwendet, um Funktionen ähnlich wie in Python zu definieren.
In Cython mit dem def
Schlüsselwort Python geschriebene Funktionen sind für anderen Python-Code sichtbar, verursachen jedoch Leistungseinbußen. Funktionen, die das cdef
Schlüsselwort verwenden, sind nur für anderen Cython- oder C-Code sichtbar, werden jedoch viel schneller ausgeführt. Wenn Sie Funktionen haben, die nur intern innerhalb eines Cython-Moduls aufgerufen werden, verwenden Sie cdef
.
Ein drittes Schlüsselwort cpdef
bietet Kompatibilität mit Python-Code und C-Code, sodass C-Code mit voller Geschwindigkeit auf die deklarierte Funktion zugreifen kann. Diese Bequemlichkeit ist jedoch mit Kosten verbunden: cpdef
Funktionen generieren mehr Code und haben etwas mehr Anrufaufwand als cdef
.
Andere Cython-Schlüsselwörter
Andere Schlüsselwörter in Cython bieten Kontrolle über Aspekte des Programmflusses und -verhaltens, die in Python nicht verfügbar sind:
gil
undnogil
. Hierbei handelt es sich um Kontextmanager, mit denen Codeabschnitte beschrieben werden, für die (with gil:
) oder nicht (with nogil:
) Pythons Global Interpreter Lock oder GIL erforderlich ist . C-Code, der keine Aufrufe der Python-APInogil
ausführt, kann in einem Block schneller ausgeführt werden , insbesondere wenn er einen lang laufenden Vorgang ausführt, z. B. das Lesen von einer Netzwerkverbindung.cimport
. Dadurch wird Cython angewiesen, C-Datentypen, Funktionen, Variablen und Erweiterungstypen zu importieren. Cython-Apps, die beispielsweise die nativen C-Module von NumPy verwendencimport
, erhalten Zugriff auf diese Funktionen.include
. Dadurch wird der Quellcode einer Cython-Datei ähnlich wie in C in einer anderen Datei abgelegt. Beachten Sie, dass Cython eine ausgefeiltere Methode zum Teilen von Deklarationen zwischen anderen Cython-Dateien als nurinclude
s bietet .ctypedef
. Wird verwendet, um auf Typdefinitionen in externen C-Header-Dateien zu verweisen.extern
. Wird verwendetcdef
, um auf C-Funktionen oder Variablen in anderen Modulen zu verweisen.public/api
. Wird verwendet, um Deklarationen in Cython-Modulen abzugeben, die für anderen C-Code sichtbar sind.inline
. Wird verwendet, um anzugeben, dass eine bestimmte Funktion aus Gründen der Geschwindigkeit inline gesetzt oder in den Hauptteil der aufrufenden Funktion eingefügt werden soll, wenn sie verwendet wird. Zum Beispiel könnte dief
Funktion im obigen Codebeispiel mit dekoriert werdeninline
, um den Funktionsaufruf-Overhead zu reduzieren, da sie nur an einer Stelle verwendet wird. (Beachten Sie, dass der C-Compiler möglicherweise automatisch ein eigenes Inlining ausführt,inline
Sie jedoch explizit angeben können, ob etwas inliniert werden soll.)
It is not necessary to know all of the Cython keywords in advance. Cython code tends to be written incrementally—first you write valid Python code, then you add Cython decoration to speed it up. Thus you can pick up Cython’s extended keyword syntax piecemeal, as you need it.
Compile Cython
Now that we have some idea of what a simple Cython program looks like and why it looks the way it does, let’s walk through the steps needed to compile Cython into a working binary.
To build a working Cython program, we will need three things:
- The Python interpreter. Use the most recent release version, if you can.
- The Cython package. You can add Cython to Python by way of the
pip
package manager:pip install cython
- A C compiler.
Item #3 can be tricky if you’re using Microsoft Windows as your development platform. Unlike Linux, Windows doesn’t come with a C compiler as a standard component. To address this, grab a copy of Microsoft Visual Studio Community Edition, which includes Microsoft’s C compiler and costs nothing.
Note that, as of this writing, the most recent release version of Cython is 0.29.16, but a beta version of Cython 3.0 is available for use. If you use pip install cython
, the most current non-beta version will be installed. If you want to try out the beta, use pip install cython>=3.0a1
to install the most recent edition of the Cython 3.0 branch. Cython’s developers recommend trying the Cython 3.0 branch whenever possible, because in some cases it generates significantly faster code.
Cython programs use the .pyx
file extension. In a new directory, create a file named num.pyx
that contains the Cython code example shown above (the second code sample under “A Cython example”) and a file named main.py
that contains the following code:
from num import integrate_fprint (integrate_f(1.0, 10.0, 2000))
This is a regular Python progam that will call the integrate_f
function found in num.pyx
. Python code “sees” Cython code as just another module, so you don’t need to do anything special other than import the compiled module and run its functions.
Finally, add a file named setup.py
with the following code:
from distutils.core import setup from distutils.extension import Extension from Cython.Build import cythonize ext_modules = [ Extension( r'num', [r'num.pyx'] ), ] setup( name="num", ext_modules=cythonize(ext_modules),)
setup.py
is normally used by Python to install the module it’s associated with, and can also be used to direct Python to compile C extensions for that module. Here we’re using setup.py
to compile Cython code.
If you’re on Linux, and you have a C compiler installed (typically the case), you can compile the .pyx
file to C by running the command:
python setup.py build_ext --inplace
If you’re using Microsoft Windows and Microsoft Visual Studio 2017 or better, you’ll need to make sure you have the most recent version of setuptools
installed in Python (version 46.1.3 as of this writing) before that command will work. This ensures that Python’s build tools will be able to auto-detect and use the version of Visual Studio you have installed.
If the compilation is successful, you should see new files appear in the directory: num.c
(the C file generated by Cython) and a file with either a .o
extension (on Linux) or a .pyd
extension (on Windows). That’s the binary that the C file has been compiled into. You may also see a \build
subdirectory, which contains the artifacts from the build process.
Run python main.py
, and you should see something like the following returned as a response:
283.297530375
That’s the output from the compiled integral function, as invoked by our pure Python code. Try playing with the parameters passed to the function in main.py
to see how the output changes.
Note that whenever you make changes to the .pyx
file, you will need to recompile it. (Any changes you make to conventional Python code will take effect immediately.)
The resulting compiled file has no dependencies except the version of Python it was compiled for, and so can be bundled into a binary wheel. Note that if you refer to other libraries in your code, like NumPy (see below), you will need to provide those as part of the application’s requirements.
How to use Cython
Now that you know how to “Cythonize” a piece of code, the next step is to determine how your Python application can benefit from Cython. Where exactly should you apply it?
For best results, use Cython to optimize these kinds of Python functions:
- Functions that run in tight loops, or require long amounts of processing time in a single “hot spot” of code.
- Functions that perform numerical manipulations.
- Functions that work with objects that can be represented in pure C, such as basic numerical types, arrays, or structures, rather than Python object types like lists, dictionaries, or tuples.
Python has traditionally been less efficient at loops and numerical manipulations than other, non-interpreted languages. The more you decorate your code to indicate it should use base numerical types that can be turned into C, the faster it will do number-crunching.
Using Python object types in Cython isn’t itself a problem. Cython functions that use Python objects will still compile, and Python objects may be preferable when performance isn’t the top consideration. But any code that makes use of Python objects will be limited by the performance of the Python runtime, as Cython will generate code to directly address Python’s APIs and ABIs.
Another worthy target of Cython optimization is Python code that interacts directly with a C library. You can skip the Python “wrapper” code and interface with the libraries directly.
However, Cython does not automatically generate the proper call interfaces for those libraries. You will need to have Cython refer to the function signatures in the library’s header files, by way of a cdef extern from
declaration. Note that if you don’t have the header files, Cython is forgiving enough to let you declare external function signatures that approximate the original headers. But use the originals whenever possible to be safe.
One external C library that Cython can use right out of the box is NumPy. To take advantage of Cython’s fast access to NumPy arrays, use cimport numpy
(optionally with as np
to keep its namespace distinct), and then use cdef
statements to declare NumPy variables, such as cdef np.array
or np.ndarray
.
Cython profiling
The first step to improving an application’s performance is to profile it—to generate a detailed report of where the time is being spent during execution. Python provides built-in mechanisms for generating code profiles. Cython not only hooks into those mechanisms but has profiling tools of its own.
Python’s own profiler, cProfile
, generates reports that show which functions take up the most amount of time in a given Python program. By default, Cython code doesn’t show up in those reports, but you can enable profiling on Cython code by inserting a compiler directive at the top of the .pyx
file with functions you want to include in the profiling:
# cython: profile=True
You can also enable line-by-line tracing on the C code generated by Cython, but this imposes a lot of overhead, and so is turned off by default.
Note that profiling imposes a performance hit, so be sure to toggle profiling off for code that is being shipped into production.
Cython can also generate code reports that indicate how much of a given .pyx
file is being converted to C, and how much of it remains Python code. To see this in action, edit the setup.py
file in our example and add the following two lines at the top:
import Cython.Compiler.OptionsCython.Compiler.Options.annotate = True
(Alternatively, you can use a directive in setup.py to enable annotations, but the above method is often easier to work with.)
Löschen Sie die .c
im Projekt generierten Dateien und führen Sie das setup.py
Skript erneut aus, um alles neu zu kompilieren. Wenn Sie fertig sind, sollten Sie eine HTML-Datei in demselben Verzeichnis sehen, das den Namen Ihrer .pyx-Datei teilt - in diesem Fall num.html
. Öffnen Sie die HTML-Datei und Sie sehen die Teile Ihres Codes, die noch von Python abhängig sind, gelb hervorgehoben. Sie können auf die gelben Bereiche klicken, um den von Cython generierten zugrunde liegenden C-Code anzuzeigen.
