def f(x):
y = 0.0
for i in range(100000):
y += x
return y
f(0.01)
999.9999999992356
%timeit f(0.01)
100 loops, best of 3: 5.24 ms per loop
Cython ist ein Compiler, der Cython Code in C/C++ Code übersetzt und mit der Python C-Bibliothek verbindet. Dies eignet sich hervorragend dazu, um langsame Teile eines größeren Python Programmes selektiv zu beschleunigen. Dies wird dadurch erreicht, dass einzelne Funktionen oder Klassen in die Sprache "Cython" umgeschrieben werden. Dies ist nicht besonders schwierig, da Cython eine Obermenge einer Untermenge der Python Sprache ist: das heißt, einige komplexere Programmstrukturen gibt es nicht, dafür aber Erweiterungen wie z.B. native Typenbezeichnungen.
Die einzige wirkliche Herausforderung ist, genau zu verstehen welche Datentypen im Spiel sind und wie aufwändig die eventuelle Konvertierung der einzelnen Objekte zwischen C/C++ und Python ist. In der Dokumentation gibt es dazu viele Beispiele mit anschaulichen Erklärungen.
Im folgenden wird nun Cython durch die cythonmagic
Extension geladen,
die eingangs vorgestellte Python Funktion nach Cython umgeschrieben,
und in der Zelle mittels des %%cython
Magic-Commandos ausgeführt.
Von der im Hintergrund ablaufenden Compilierung nach C, das Linken mit den Python Bibliotheken, und das anschließende dynamische laden in den aktuellen Python-Kernel bekommt man nichts mit -- es dauert nur ein paar Sekunden, die sich dann später durch die beschleunigte Ausführungszeit wieder leicht einholen lassen ;-)
%load_ext Cython
%%cython
cimport cython
@cython.boundscheck(False)
cpdef f_cy(float x):
cdef double y = 0.0
cdef int i
for 0 <= i <= 100000:
y += x
return y
Kontrolle, dass das Ergebnis stimmt und anschließendes Benchmark mit %timeit
f_cy(0.01)
1000.0099776480347
%timeit f_cy(0.01)
1000 loops, best of 3: 214 µs per loop
Die --annotate
Option zeigt in gelb genauer an,
was wo Konvertierungen oder Python-Objekte noch im Spiel sind (die das Programm langam machen):
%%cython --annotate
cimport cython
@cython.boundscheck(False)
cpdef f_cy(float x):
cdef double y = 0.0
cdef int i
for 0 <= i <= 100000:
y += x
return y
Generated by Cython 0.24
Yellow lines hint at Python interaction.
Click on a line that starts with a "+
" to see the C code that Cython generated for it.
1: cimport cython
2: @cython.boundscheck(False)
+3: cpdef f_cy(float x):
static PyObject *__pyx_pw_46_cython_magic_22a304d0c206e4ff00eb6647f587e3bb_1f_cy(PyObject *__pyx_self, PyObject *__pyx_arg_x); /*proto*/ static PyObject *__pyx_f_46_cython_magic_22a304d0c206e4ff00eb6647f587e3bb_f_cy(float __pyx_v_x, CYTHON_UNUSED int __pyx_skip_dispatch) { double __pyx_v_y; CYTHON_UNUSED int __pyx_v_i; PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("f_cy", 0); /* … */ /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); __Pyx_AddTraceback("_cython_magic_22a304d0c206e4ff00eb6647f587e3bb.f_cy", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = 0; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); __Pyx_RefNannyFinishContext(); return __pyx_r; } /* Python wrapper */ static PyObject *__pyx_pw_46_cython_magic_22a304d0c206e4ff00eb6647f587e3bb_1f_cy(PyObject *__pyx_self, PyObject *__pyx_arg_x); /*proto*/ static PyObject *__pyx_pw_46_cython_magic_22a304d0c206e4ff00eb6647f587e3bb_1f_cy(PyObject *__pyx_self, PyObject *__pyx_arg_x) { float __pyx_v_x; PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("f_cy (wrapper)", 0); assert(__pyx_arg_x); { __pyx_v_x = __pyx_PyFloat_AsFloat(__pyx_arg_x); if (unlikely((__pyx_v_x == (float)-1) && PyErr_Occurred())) __PYX_ERR(0, 3, __pyx_L3_error) } goto __pyx_L4_argument_unpacking_done; __pyx_L3_error:; __Pyx_AddTraceback("_cython_magic_22a304d0c206e4ff00eb6647f587e3bb.f_cy", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; __pyx_r = __pyx_pf_46_cython_magic_22a304d0c206e4ff00eb6647f587e3bb_f_cy(__pyx_self, ((float)__pyx_v_x)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } static PyObject *__pyx_pf_46_cython_magic_22a304d0c206e4ff00eb6647f587e3bb_f_cy(CYTHON_UNUSED PyObject *__pyx_self, float __pyx_v_x) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("f_cy", 0); __Pyx_XDECREF(__pyx_r); __pyx_t_1 = __pyx_f_46_cython_magic_22a304d0c206e4ff00eb6647f587e3bb_f_cy(__pyx_v_x, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 3, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); __pyx_r = __pyx_t_1; __pyx_t_1 = 0; goto __pyx_L0; /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); __Pyx_AddTraceback("_cython_magic_22a304d0c206e4ff00eb6647f587e3bb.f_cy", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); __Pyx_RefNannyFinishContext(); return __pyx_r; }
+4: cdef double y = 0.0
__pyx_v_y = 0.0;
5: cdef int i
+6: for 0 <= i <= 100000:
for (__pyx_v_i = 0; __pyx_v_i <= 0x186A0; __pyx_v_i++) {
+7: y += x
__pyx_v_y = (__pyx_v_y + __pyx_v_x); }
+8: return y
__Pyx_XDECREF(__pyx_r); __pyx_t_1 = PyFloat_FromDouble(__pyx_v_y); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 8, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); __pyx_r = __pyx_t_1; __pyx_t_1 = 0; goto __pyx_L0;
OpenMP ist ein älteres Programmierhilfsmittel, um Schleifen und parallelisierbare Datenstrukturen im Code einfach ausdrücken zu können.
Die Arbeit wird hierfür auf alle CPU-Kerne verteilt.
In dem hier vorgestellten Beispiel ist es die Funktion prange
, welche diese in Kombination mit der Addition der Ypsilons erledigt.
Implizit wird hier auf die Nebenläufigkeit der Berechnung und dem anschließenden zusammenführen der Ergebnisse aufgepasst!
%%cython --compile-args=-fopenmp --link-args=-fopenmp
cimport cython
from cython.parallel import parallel, prange
@cython.boundscheck(False)
cpdef f_cy_omp(float x):
cdef double y = 0.0
cdef Py_ssize_t i
with nogil:
for i in prange(100000):
y += x
return y
f_cy_omp(0.01)
999.9999776482582
%timeit f_cy_omp(0.01)
10 loops, best of 3: 47.3 ms per loop