Hervorragende Sprache für schnelle Prototypen und Codeentwicklung, aber eine Sache, die ich oft höre, wenn Leute darüber sagen, dass es nur langsam ausgeführt werden kann. Dies ist ein besonderer Schmerzpunkt für Datenwissenschaftler und ML -Ingenieure, da sie häufig rechenintensive Operationen wie die Matrix -Multiplikation, Gradientenabstichtberechnungen oder Bildverarbeitung ausführen.
Im Laufe der Zeit hat sich Python intern entwickelt, um einige dieser Probleme zu lösen, indem er neue Funktionen in die Sprache einführt, z. B. Multi-Threading oder Umschreiben bestehender Funktionen für eine verbesserte Leistung. Pythons Einsatz des World Interpreter Lock (GIL) hat jedoch häufig solche Bemühungen wie diese beeinträchtigt.
Viele externe Bibliotheken wurden auch geschrieben, um diese wahrgenommene Leistungslücke zwischen Python und kompilierten Sprachen wie Java zu überbrücken. Das vielleicht am häufigsten verwendete und bekannteste von diesen ist das Numpy Bibliothek. Numpy wurde in der C-Sprache implementiert und wurde von Grund auf entworfen, um mehrere CPU-Kerne und eine superschnelle numerische und Array-Verarbeitung zu unterstützen.
Es gibt Alternativen zu Numpy, und in einem kürzlich durchgeführten TDS -Artikel habe ich das vorgestellt numexpr Bibliothek, die in vielen Anwendungsfällen Numpy sogar übertreffen kann. Wenn Sie mehr erfahren möchten, werde ich am Ende dieses Artikels einen Hyperlink zu dieser Geschichte aufnehmen.
Eine andere externe Bibliothek, die sehr effektiv ist, ist Numba. Numba verwendet einen Simply-in-Time-Compiler für Python, der zur Laufzeit eine Untergruppe von Python- und Numpy-Code in schnellen Maschinencode übersetzt. Es wurde entwickelt, um numerische und wissenschaftliche Computeraufgaben zu beschleunigen, indem LLVM-Compiler-Infrastruktur (Low-Degree Digital Machine) eingesetzt wird.
In diesem Artikel möchte ich eine weitere laufendempfantische externe Bibliothek diskutieren. Cython. Es ist eine der leistungsstärksten Python -Bibliotheken, aber auch eines der am wenigsten verstanden und verwendeten. Ich denke, das liegt zumindest teilweise daran, dass Sie die Hände ein bisschen schmutzig machen und einige Änderungen an Ihrem ursprünglichen Code vornehmen müssen. Wenn Sie jedoch dem einfachen vierstufigen Plan folgen, den ich unten skizzieren werde, werden die Leistungsvorteile, die Sie erzielen können, ihn mehr als lohnenswert machen.
Was ist Cython?
Wenn Sie noch nicht von Cython gehört haben, ist es ein Superet von Python, der C-ähnliche Leistung mit Code liefern soll, das hauptsächlich in Python geschrieben wurde. Es ermöglicht das Konvertieren von Python -Code in C -Code, der dann in gemeinsam genutzte Bibliotheken zusammengestellt werden kann, die wie reguläre Python -Module in Python importiert werden können. Dieser Prozess führt zu den Leistungsvorteilen von C und beibehalten der Lesbarkeit von Python.
Ich werde die genauen Vorteile präsentieren, die Sie erzielen können, indem Sie Ihren Code in die Verwendung von Cython konvertieren, drei Anwendungsfälle untersuchen und die vier Schritte zur Konvertierung Ihres vorhandenen Python -Codes sowie die vergleichenden Timings für jeden Lauf bereitstellen.
Einrichtung einer Entwicklungsumgebung
Bevor wir fortfahren, sollten wir eine separate Entwicklungsumgebung für die Codierung einrichten, um unsere Projektabhängigkeiten getrennt zu halten. Ich werde WSL2 Ubuntu für Home windows und ein Jupyter -Notizbuch für die Codeentwicklung verwenden. Ich benutze den UV -Paket -Supervisor, um meine Entwicklungsumgebung einzurichten. Verwenden Sie jedoch gerne alle Instruments und Methoden.
$ uv init cython-test
$ cd cython-test
$ uv venv
$ supply .venv/bin/activate
(cython-test) $ uv pip set up cython jupyter numpy pillow matplotlib
JetztTyp „Jupyter Pocket book“ in Ihre Eingabeaufforderung. Sie sollten ein Notizbuch in Ihrem Browser offen sehen. Wenn dies nicht automatisch passiert, wird nach dem Ausführen des Befehls von Jupyter Pocket book wahrscheinlich Informationen zu sehen. In der Nähe des unteren Rahmens gibt es eine URL, die Sie kopieren und in Ihren Browser einfügen sollten, um das Jupyter -Notizbuch zu initiieren.
Ihre URL wird anders sein als meine, aber sie sollte ungefähr so aussehen:-
http://127.0.0.1:8888/tree?token=3b9f7bd07b6966b41b68e2350721b2d0b6f388d248cc69d
Beispiel 1 – Beschleunigung für Schleifen
Bevor wir mit Cython beginnen, beginnen wir mit einer regelmäßigen Python -Funktion und Zeit, wie lange es dauert, um zu laufen. Dies wird unsere Foundation -Benchmark sein.
Wir werden eine einfache Doppel-für-Schleifen-Funktion codieren, die einige Sekunden dauert, um auszuführen, und dann Cython verwenden, um sie zu beschleunigen und die Laufzeitunterschiede zwischen den beiden Methoden zu messen.
Hier ist unser Foundation -Customary -Python -Code.
# sum_of_squares.py
import timeit
# Outline the usual Python perform
def slow_sum_of_squares(n):
complete = 0
for i in vary(n):
for j in vary(n):
complete += i * i + j * j
return complete
# Benchmark the Python perform
print("Python perform execution time:")
print("timeit:", timeit.timeit(
lambda: slow_sum_of_squares(20000),
quantity=1))
Auf meinem System erzeugt der obige Code die folgende Ausgabe.
Python perform execution time:
13.135973724005453
Mal sehen, wie viel Verbesserung Cython daraus macht.
Der vierstufige Plan für einen effektiven Cython-Gebrauch.
Die Verwendung von Cython, um Ihre Code-Laufzeit in einem Jupyter-Notizbuch zu steigern, ist ein einfacher 4-Stufen-Prozess.
Machen Sie sich keine Sorgen, wenn Sie kein Notizbuchbenutzer sind, da ich zeige, wie Sie regelmäßige Python -.Py -Dateien konvertieren, um Cython später zu verwenden.
1/ In der ersten Zelle Ihres Notebooks laden Sie die Cython -Erweiterung, indem Sie diesen Befehl eingeben.
%load_ext Cython
2/ für nachfolgende Zellen, die Python -Code enthalten, den Sie mit Cython ausführen möchtenAnwesend fügen Sie die hinzu %% Cython Zauberer Befehl vor dem Code. Zum Beispiel,
%%cython
def myfunction():
and so on ...
...
3/ Funktionsdefinitionen, die Parameter enthalten, müssen korrekt eingegeben werden.
4/ Zuletzt müssen alle Variablen mit der Verwendung angemessen tippt werden CDEF Richtlinie. Verwenden Sie auch, wo es sinnvoll ist, Funktionen aus der Customary -C -Bibliothek (in Cython verfügbar mit dem von libc.stdlib Richtlinie).
Wenn Sie unseren ursprünglichen Python -Code als Beispiel nutzen, muss er so aussehen, dass er nach der Anwendung aller vier Schritte in einem Notizbuch mit Cython ausgeführt wird.
%%cython
def fast_sum_of_squares(int n):
cdef int complete = 0
cdef int i, j
for i in vary(n):
for j in vary(n):
complete += i * i + j * j
return complete
import timeit
print("Cython perform execution time:")
print("timeit:", timeit.timeit(
lambda: fast_sum_of_squares(20000),
quantity=1))
Wie ich hoffe, dass Sie sehen können, ist die Realität, Ihren Code zu konvertieren, viel einfacher als die vier erforderlichen Verfahrensschritte.
Die Laufzeit des obigen Code conflict beeindruckend. In meinem System erzeugt dieser neue Cython -Code die folgende Ausgabe.
Cython perform execution time:
0.15829777799808653
Das ist eine Geschwindigkeit von über 80x.
Beispiel 2 – Berechnen Sie PI mit Monte Carlo
In unserem zweiten Beispiel werden wir einen komplexeren Anwendungsfall untersuchen, dessen Fundament zahlreiche reale Anwendungen enthält.
Ein Bereich, in dem Cython eine signifikante Leistungsverbesserung aufweisen kann, befindet sich in numerischen Simulationen, insbesondere in solchen, die schwere Berechnungen beinhalten, wie z. B. Monte -Carlo -Simulationen (MC). Monte -Carlo -Simulationen umfassen viele Iterationen eines zufälligen Prozesses, um die Eigenschaften eines Programs abzuschätzen. MC gilt für eine Vielzahl von Studienbereichen, einschließlich Klima- und Atmosphärischen Wissenschaft, Computergrafik, AI -Suche und quantitative Finanzierung. Es ist quick immer ein sehr rechenintensiver Prozess.
Zur Veranschaulichung werden wir Monte Carlo vereinfacht verwenden, um den Wert von PI zu berechnen. Dies ist ein bekanntes Beispiel, bei dem wir ein Quadrat mit einer Seitenlänge einer Einheit einnehmen und einen Viertelkreis mit einem Radius einer Einheit einschreiben, wie hier gezeigt.

Das Verhältnis der Fläche des Viertelkreises zur Fläche des Quadrats ist offensichtlich (PI/4).
Wenn wir additionally viele zufällige (x, y) Punkte betrachten, die alle innerhalb oder an den Grenzen des Quadrats liegen, da die Gesamtzahl dieser Punkte tendenziell unendlich ist, ist das Verhältnis der Punkte, die auf oder innerhalb des Viertelkreises liegen, zur Gesamtzahl der Punkte zu PI /4. Wir multiplizieren diesen Wert dann mit 4, um den Wert von PI selbst zu erhalten.
Hier ist ein typischer Python -Code, den Sie möglicherweise verwenden, um dies zu modellieren.
import random
import time
def monte_carlo_pi(num_samples):
inside_circle = 0
for _ in vary(num_samples):
x = random.uniform(0, 1)
y = random.uniform(0, 1)
if (x**2) + (y**2) <= 1:
inside_circle += 1
return (inside_circle / num_samples) * 4
# Benchmark the usual Python perform
num_samples = 100000000
start_time = time.time()
pi_estimate = monte_carlo_pi(num_samples)
end_time = time.time()
print(f"Estimated Pi (Python): {pi_estimate}")
print(f"Execution Time (Python): {end_time - start_time} seconds")
Durch das Ausführen dieses Ausführens führte das folgende Timing -Ergebnis.
Estimated Pi (Python): 3.14197216
Execution Time (Python): 20.67279839515686 seconds
Hier ist hier die Cython-Implementierung, die wir erhalten, indem wir unserem vierstufigen Prozess folgen.
%%cython
import cython
import random
from libc.stdlib cimport rand, RAND_MAX
@cython.boundscheck(False)
@cython.wraparound(False)
def monte_carlo_pi(int num_samples):
cdef int inside_circle = 0
cdef int i
cdef double x, y
for i in vary(num_samples):
x = rand() / <double>RAND_MAX
y = rand() / <double>RAND_MAX
if (x**2) + (y**2) <= 1:
inside_circle += 1
return (inside_circle / num_samples) * 4
import time
num_samples = 100000000
# Benchmark the Cython perform
start_time = time.time()
pi_estimate = monte_carlo_pi(num_samples)
end_time = time.time()
print(f"Estimated Pi (Cython): {pi_estimate}")
print(f"Execution Time (Cython): {end_time - start_time} seconds")
Und hier ist die neue Ausgabe.
Estimated Pi (Cython): 3.1415012
Execution Time (Cython): 1.9987852573394775 seconds
Wieder einmal ist das eine ziemlich beeindruckende 10-fache Geschwindigkeit für die Cython-Model.
Eine Sache, die wir in diesem Code -Beispiel gemacht haben, das wir in der anderen nicht getan haben, ist, einige externe Bibliotheken aus der C -Standardbibliothek zu importieren. Das conflict die Linie,
from libc.stdlib cimport rand, RAND_MAX
Der Cimport Der Befehl ist ein Cython -Schlüsselwort, das zum Importieren von C -Funktionen, Variablen, Konstanten und Typen verwendet wird. Wir haben es verwendet, um optimierte C -Sprachversionen der äquivalenten zufälligen () Python -Funktionen zu importieren.
Beispiel 3 – Bildmanipulation
Für unser letztes Beispiel werden wir eine Bildmanipulation durchführen. Insbesondere eine Bildverteilung, die eine gemeinsame Operation in der Bildverarbeitung ist. Es gibt viele Anwendungsfälle für die Bildverarbeitung. Wir werden es verwenden, um zu versuchen, das unten gezeigte leicht verschwommene Bild zu schärfen.

Erstens ist hier der reguläre Python -Code.
from PIL import Picture
import numpy as np
from scipy.sign import convolve2d
import time
import os
import matplotlib.pyplot as plt
def sharpen_image_color(picture):
# Begin timing
start_time = time.time()
# Convert picture to RGB in case it isn't already
picture = picture.convert('RGB')
# Outline a sharpening kernel
kernel = np.array(((0, -1, 0),
(-1, 5, -1),
(0, -1, 0)))
# Convert picture to numpy array
image_array = np.array(picture)
# Debugging: Test enter values
print("Enter array values: Min =", image_array.min(), "Max =", image_array.max())
# Put together an empty array for the sharpened picture
sharpened_array = np.zeros_like(image_array)
# Apply the convolution kernel to every channel (assuming RGB picture)
for i in vary(3):
channel = image_array(:, :, i)
# Carry out convolution
convolved_channel = convolve2d(channel, kernel, mode='similar', boundary='wrap')
# Clip values to be within the vary (0, 255)
convolved_channel = np.clip(convolved_channel, 0, 255)
# Retailer again within the sharpened array
sharpened_array(:, :, i) = convolved_channel.astype(np.uint8)
# Debugging: Test output values
print("Sharpened array values: Min =", sharpened_array.min(), "Max =", sharpened_array.max())
# Convert array again to picture
sharpened_image = Picture.fromarray(sharpened_array)
# Finish timing
period = time.time() - start_time
print(f"Processing time: {period:.4f} seconds")
return sharpened_image
# Appropriate path for WSL2 accessing Home windows filesystem
image_path = '/mnt/d/photographs/taj_mahal.png'
picture = Picture.open(image_path)
# Sharpen the picture
sharpened_image = sharpen_image_color(picture)
if sharpened_image:
# Present utilizing PIL's built-in present methodology (for debugging)
#sharpened_image.present(title="Sharpened Picture (PIL Present)")
# Show the unique and sharpened photographs utilizing Matplotlib
fig, axs = plt.subplots(1, 2, figsize=(15, 7))
# Authentic picture
axs(0).imshow(picture)
axs(0).set_title("Authentic Picture")
axs(0).axis('off')
# Sharpened picture
axs(1).imshow(sharpened_image)
axs(1).set_title("Sharpened Picture")
axs(1).axis('off')
# Present each photographs facet by facet
plt.present()
else:
print("Didn't generate sharpened picture.")
Die Ausgabe ist das.
Enter array values: Min = 0 Max = 255
Sharpened array values: Min = 0 Max = 255
Processing time: 0.1034 seconds

Mal sehen, ob Cython diese Laufzeit von 0,1034 Sekunden übertreffen kann.
%%cython
# cython: language_level=3
# distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION
import numpy as np
cimport numpy as np
import cython
@cython.boundscheck(False)
@cython.wraparound(False)
def sharpen_image_cython(np.ndarray(np.uint8_t, ndim=3) image_array):
# Outline sharpening kernel
cdef int kernel(3)(3)
kernel(0)(0) = 0
kernel(0)(1) = -1
kernel(0)(2) = 0
kernel(1)(0) = -1
kernel(1)(1) = 5
kernel(1)(2) = -1
kernel(2)(0) = 0
kernel(2)(1) = -1
kernel(2)(2) = 0
# Declare variables exterior of loops
cdef int top = image_array.form(0)
cdef int width = image_array.form(1)
cdef int channel, i, j, ki, kj
cdef int worth
# Put together an empty array for the sharpened picture
cdef np.ndarray(np.uint8_t, ndim=3) sharpened_array = np.zeros_like(image_array)
# Convolve every channel individually
for channel in vary(3): # Iterate over RGB channels
for i in vary(1, top - 1):
for j in vary(1, width - 1):
worth = 0 # Reset worth at every pixel
# Apply the kernel
for ki in vary(-1, 2):
for kj in vary(-1, 2):
worth += kernel(ki + 1)(kj + 1) * image_array(i + ki, j + kj, channel)
# Clip values to be between 0 and 255
sharpened_array(i, j, channel) = min(max(worth, 0), 255)
return sharpened_array
# Python a part of the code
from PIL import Picture
import numpy as np
import time as py_time # Renaming the Python time module to keep away from battle
import matplotlib.pyplot as plt
# Load the enter picture
image_path = '/mnt/d/photographs/taj_mahal.png'
picture = Picture.open(image_path).convert('RGB')
# Convert the picture to a NumPy array
image_array = np.array(picture)
# Time the sharpening with Cython
start_time = py_time.time()
sharpened_array = sharpen_image_cython(image_array)
cython_time = py_time.time() - start_time
# Convert again to a picture for displaying
sharpened_image = Picture.fromarray(sharpened_array)
# Show the unique and sharpened picture
plt.determine(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(picture)
plt.title("Authentic Picture")
plt.subplot(1, 2, 2)
plt.imshow(sharpened_image)
plt.title("Sharpened Picture")
plt.present()
# Print the time taken for Cython processing
print(f"Processing time with Cython: {cython_time:.4f} seconds")
Die Ausgabe ist,

Beide Programme haben sich intestine entwickelt, aber Cython conflict quick 25 -mal schneller.
Was ist mit Cython außerhalb einer Pocket book -Umgebung?
Bisher geht alles, was ich gezeigt habe, an, dass Sie Ihren Code in einem Jupyter -Notizbuch ausführen. Der Grund, warum ich dies getan habe, ist, dass es der einfachste Weg ist, Cython einzuführen und schnell einen Code in Betrieb zu nehmen. Während die Pocket book -Umgebung bei Python -Entwicklern äußerst beliebt ist, ist eine große Menge an Python -Code in regulären .py -Dateien immer noch enthalten und wird mit dem Befehl python aus einem Terminal ausgeführt.
Wenn dies Ihre primäre Artwork des Codierens und Ausführens von Python -Skripten ist, ist die %load_ext Und %% Cython Ipython Magic -Befehle funktionieren nicht, da diese nur von Jupyter/Ipython verstanden werden.
Hier erfahren Sie, wie Sie meinen vierstufigen Cython Conversion-Prozess anpassen, wenn Sie Ihren Code als reguläres Python-Skript ausführen.
Nehmen wir meinen ersten sum_of_squares Beispiel, um dies zu demonstrieren.
1/ Erstellen Sie eine .pyx -Datei anstatt %% Cython zu verwenden
Bewegen Sie Ihren Cython-verstärkten Code in eine Datei namens, zum Beispiel:-
sum_of_squares.pyx
# sun_of_squares.pyx
def fast_sum_of_squares(int n):
cdef int complete = 0
cdef int i, j
for i in vary(n):
for j in vary(n):
complete += i * i + j * j
return complete
Wir haben nur die %% Cython -Richtlinie und den Timing -Code entfernt (der sich jetzt in der Aufruffunktion befindet).
2/ Erstellen Sie eine Setup.py -Datei, um Ihre .pyx -Datei zu kompilieren
# setup.py
from setuptools import setup
from Cython.Construct import cythonize
setup(
title="cython-test",
ext_modules=cythonize("sum_of_squares.pyx", language_level=3),
py_modules=("sum_of_squares"), # Explicitly state the module
zip_safe=False,
)
3/ Führen Sie die Datei setup.py mit diesem Befehl aus.
$ python setup.py build_ext --inplace
operating build_ext
copying construct/lib.linux-x86_64-cpython-311/sum_of_squares.cpython-311-x86_64-linux-g
4/ Erstellen Sie ein reguläres Python -Modul, um unseren Cython -Code wie unten gezeigt aufzurufen und dann auszuführen.
# principal.py
import time, timeit
from sum_of_squares import fast_sum_of_squares
begin = time.time()
end result = fast_sum_of_squares(20000)
print("timeit:", timeit.timeit(
lambda: fast_sum_of_squares(20000),
quantity=1))
$ python principal.py
timeit: 0.14675087109208107
Zusammenfassung
Hoffentlich habe ich Sie von der Wirksamkeit der Verwendung der Cython -Bibliothek in Ihrem Code überzeugt. Obwohl es auf den ersten Blick mit ein wenig Aufwand ein bisschen kompliziert erscheinen magazine, können Sie Ihre Laufzeiten mit regelmäßiger Python unglaubliche Leistungsverbesserungen erzielen, selbst wenn Sie schnelle numerische Bibliotheken wie Numpy verwenden.
Ich habe einen vierstufigen Prozess bereitgestellt, um Ihren regulären Python-Code für das Ausführen von Jupyter-Pocket book-Umgebungen zu konvertieren. Darüber hinaus habe ich die Schritte erläutert, die erforderlich sind, um den Cython -Code aus der Befehlszeile außerhalb einer Pocket book -Umgebung auszuführen.
Schließlich verstärkte ich das Obige, indem ich Beispiele für die Konvertierung des regelmäßigen Python -Codes zur Verwendung von Cython präsentierte.
In den drei Beispielen, die ich zeigte, erzielten wir Gewinne von 80x, 10x und 25x beschleunigten, was überhaupt nicht zu schäbig ist.
Wie versprochen ist hier ein Hyperlink Zu meinem vorherigen TDS -Artikel über die Verwendung der NumexPR -Bibliothek, um den Python -Code zu beschleunigen.
