Cython is a Python superset that compiles to C. It is useful when a measured bottleneck needs custom, faster code that still works with Python and NumPy. Compilation alone is not the optimization: the expensive path must avoid Python-object operations. section 5.0 section 5.8
Start with an image filter
A baseline filter converts each RGB pixel to grayscale, then darkens it using a second image. Python code loads the images and imports a .pyx module through pyximport, which compiles and links it. The first Cython implementation still uses NumPy operations inside nested pixel loops; it establishes a comparison, not a guaranteed speedup. Exclude import-time compilation when measuring the filter. section 5.2 section 5.2.1
Optimize the measured hot path in stages:
- Declare argument and local types. An annotated integer addition becomes native C arithmetic in the generated implementation, although calls from Python still cross a wrapper. Use def for a Python interface, cdef for Cython-only calls, or cpdef for both; keep intensive work behind the Python-facing boundary. section 5.2.3 section 5.2.4
- Profile again rather than guessing. Enable linetrace and compile with CYTHON_TRACE for line_profiler; its instrumentation changes absolute timings. Use cython -a to locate remaining interactions with Python. section 5.3.2 section 5.4
- Convert NumPy arrays to typed memoryviews at the boundary, then pass views into the inner function. Memoryviews expose array data, shape, and strides without making each access a Python-object operation; continue profiling, because other interactions can remain. section 5.4 section 5.4.1
- Choose an execution pattern that fits the algorithm. A Cython NumPy generalized universal function handles pixel-by-pixel work and gains broadcasting, but does not suffice when a result needs other array positions. For threaded loops, first remove GIL-dependent calls, then use nogil and Cython’s OpenMP-based prange. section 5.5 section 5.5.1 section 5.7
#cython: language_level=3
def add4(my_number):
i = my_number + 4
return i
def add4_annotated(int my_number):
cdef int i
i = my_number + 4
return i
Compare the untyped and annotated add4 functions before inspecting their generated C: the small declaration changes what happens inside the implementation. section 5.2.3
When pixels depend on neighbors
A color Game of Life needs neighboring cells, so its core uses typed array views rather than an elementwise universal function. An extended board copies opposite edges into a border, avoiding boundary tests in the inner loop at a memory cost. For a 1,000 × 1,000 board over 200 generations, the reported Python run took just under 1,000 seconds versus 2.5 seconds for Cython. Larger, differently shaped boards also produced markedly different timings despite equal cell counts: benchmark the actual layout and workload. section 5.6 section 5.6.1 section 5.6.2
Cython is one option, not an automatic winner over existing libraries or Numba. With pandas, the route is through its NumPy arrays, not a direct Cython–pandas interface; measure competing approaches for the data and hardware at hand. section 5.1 section 7.3.3
