Cython Advanced¶
When you'd use this
Static types, .pyx files, typed memoryviews, wrapping C libraries and compilation.
Compile Python to C for big speedups on hot numeric loops, or to wrap C libraries.
What is Cython?¶
Introduces Cython and where it fits in Cython.
Cython is a superset of Python that compiles to C. Adding type annotations gives C-level speed while keeping Python-like syntax.
Typical speedup: 10x–100x for numerical loops.
Setup¶
Compile .pyx modules into C extensions as part of your build.
# setup.py
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize("my_module.pyx", annotate=True),
)
Basic Cython — adding types¶
Annotate variables with C types to remove Python overhead from hot loops.
# fib.pyx
def fib_python(n):
"""Pure Python — baseline."""
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
def fib_cython(int n):
"""Cython with typed variables — 50-100x faster."""
cdef long long a = 0, b = 1
cdef int i
for i in range(n):
a, b = b, a + b
return a
# Benchmark
import timeit
from fib import fib_python, fib_cython
print(timeit.timeit("fib_python(1000)", globals=globals(), number=10000)) # ~3.5s
print(timeit.timeit("fib_cython(1000)", globals=globals(), number=10000)) # ~0.05s
# 70x speedup!
Type declarations¶
Declare cdef types so arithmetic runs at C speed.
# Variable types
cdef int x = 10
cdef double y = 3.14
cdef long long big_num = 10**18
cdef bint flag = True # C boolean (0 or 1)
# Function types
cdef int add(int a, int b): # C function — not callable from Python
return a + b
cpdef int add_public(int a, int b): # callable from both C and Python
return a + b
def add_python(int a, int b): # Python function with typed args
return a + b
| Keyword | Callable from Python? | Callable from Cython? | Speed |
|---|---|---|---|
def | Yes | Yes | Python speed (unless typed args) |
cdef | No | Yes | C speed |
cpdef | Yes | Yes | C speed (with Python wrapper) |
Typed memoryviews (fast array access)¶
Access NumPy/array buffers directly without Python indexing overhead.
# primes.pyx
import numpy as np
cimport numpy as cnp
def find_primes(int limit):
"""Sieve of Eratosthenes — fast with typed memoryview."""
cdef cnp.uint8_t[:] is_prime = np.ones(limit + 1, dtype=np.uint8)
cdef int i, j
is_prime[0] = 0
is_prime[1] = 0
for i in range(2, int(limit**0.5) + 1):
if is_prime[i]:
for j in range(i*i, limit + 1, i):
is_prime[j] = 0
return [i for i in range(limit + 1) if is_prime[i]]
Memoryview vs NumPy indexing:¶
# SLOW — Python object indexing
def slow(numpy_array):
cdef int i
s = 0
for i in range(len(numpy_array)):
s += numpy_array[i] # Python __getitem__ each time!
return s
# FAST — typed memoryview (direct memory access)
def fast(double[:] arr):
cdef int i
cdef double s = 0
for i in range(arr.shape[0]):
s += arr[i] # direct C pointer arithmetic!
return s
Parallel loops with prange¶
Release the GIL and run loops across cores from Cython.
# parallel.pyx
from cython.parallel import prange
import numpy as np
def compute_parallel(double[:] data, int n):
"""Parallel computation — releases GIL!"""
cdef int i
cdef double[:] result = np.empty(n)
# nogil + prange = true parallel execution
with nogil:
for i in prange(n):
result[i] = data[i] ** 2 + data[i] * 3.14
return np.asarray(result)
Compile with OpenMP:
# setup.py
from setuptools import setup, Extension
from Cython.Build import cythonize
ext = Extension(
"parallel",
sources=["parallel.pyx"],
extra_compile_args=["-fopenmp"],
extra_link_args=["-fopenmp"],
)
setup(ext_modules=cythonize([ext]))
Wrapping C libraries¶
Call existing C code from Python by declaring its interface in Cython.
# wrapper.pyx
# Declare the C function
cdef extern from "math.h":
double sin(double x)
double cos(double x)
double sqrt(double x)
# Python-accessible wrapper
def py_sin(double x):
return sin(x)
# Wrap a custom C library
cdef extern from "mylib.h":
int fast_hash(const char* data, int length)
void process_buffer(double* buf, int size)
def hash_string(str s):
cdef bytes encoded = s.encode('utf-8')
return fast_hash(encoded, len(encoded))
The annotation report¶
The annotation report — a key concept in Cython.
The HTML shows each line colored: - White → pure C (fast) - Yellow → Python interaction (slow)
Goal: make hot loops white (no yellow lines).
When to use Cython vs alternatives¶
A core question explored in Cython: When to use Cython vs alternatives.
| Tool | Best for | Effort | Speedup |
|---|---|---|---|
| Cython | Existing Python code, wrapping C | Medium | 10-100x |
| Numba | NumPy-heavy numerical code | Low | 10-100x |
| PyO3 (Rust) | New high-perf modules | High | 50-200x |
| ctypes/cffi | Calling existing C libraries | Low | Depends |
| NumPy vectorization | Array operations | Very low | 5-50x |
Practice Exercises¶
- Cythonize a numerical function and measure the speedup vs pure Python.
- Use typed memoryviews to speed up element-wise operations on large arrays.
- Wrap a C library (e.g., zlib compression) with Cython.
- Use prange for a parallel computation and verify it uses multiple cores.
- Read the annotation report and eliminate all yellow lines from a function.
- Compare Cython vs Numba vs pure NumPy for the same computation.
💬 Discussion
Have a question about this topic? Found an error? Share your thoughts below.