Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
9 views

Cython

Uploaded by

xilvenkat
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views

Cython

Uploaded by

xilvenkat
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 35

Introduction to Cython

SoHPC 2021
James Nelson
Outline

1. Cython Fundamentals

2. Accelerating Cython

3. Putting Everything Together

4. Summary

2
Cython
Fundamentals
3
What is Cython?
▪ Cython is a programming language that makes writing C extensions for the Python language as
easy as Python itself. The source code gets translated into optimised C/C++ code and compiled as
Python extension modules.
▪ The code is executed in the CPython runtime environment, but at the speed of compiled C with
the ability to call directly into C libraries, whilst keeping the original interface of the Python source
code.

extending the CPython


interpreter with fast binary
modules
Cython Use Cases
interfacing Python code with
external C libraries

4
▪ Cython IS Python, just with C data types (so some basic C knowledge is recommended)
Typing
Cython supports static type declarations, thereby turning readable Python code into plain C performance.
Static Typing:
▪ Type checking is performed during compile-time
▪ e.g. x = 4 + 'e' would not compile
▪ Can detect type errors in rarely used code paths
▪ Must declare data types of variables
▪ Examples: C, C++, Java
Dynamic Typing:
▪ Type checking is performed during run-time
▪ e.g. x = 4 + 'e' would result in a runtime type error
▪ Don’t have to declare data types of variables
▪ Examples: Python
This allows for fast program execution and tight integration with external C libraries.
5
Implementing Cython
Three Steps:

1. Write the python code (which is to be Cythonized) to a .pyx file

2. Create a setup.py file to compile the code to a .so file (shared object)

3. Import the shared object

6
Implementing Cython
def fib_func(n):
# Prints the Fibonacci series up to n.
a, b = 0, 1
while b < n:
print(b)
a, b = b, a + b

This function is written


to fib.pyx

Demo 1
7
Implementing Cython
▪ The next step is to create the setup.py file
▪ Throughout this tutorial it will always have the same format

from distutils.core import setup In this case the file


from Cython.Build import cythonize name will be
“fib.pyx”
setup(
ext_modules=cythonize(<file_name>)
)

Demo 1
8
Implementing Cython
We use build_ext --inplace to
▪ Now we compile the code compile the extension for use in
the current directory

python setup.py build_ext --inplace

▪ This creates a:
▫ .c file, which is then compiled using a C compiler
▫ build directory which contains the .o file generated by the compiler
▫ .so file. The compiled library file

Demo 1
9
Implementing Cython
▪ Finally import the Cython file
▪ Just like importing a python module

from fib import fib_func

fib_func(10)

Demo 1
10
Implementing Cython
def fib_func(n):
# Prints the Fibonacci series up to n. from fib import fib_func
a, b = 0, 1
while b < n: fib_func(10)
print(b)
a, b = b, a + b fib.pyx

from distutils.core import setup


from Cython.Build import cythonize

setup( python setup.py build_ext --inplace


ext_modules=cythonize(“fib.pyx”)
)
setup.py
Demo 1
11
Accelerating Cython
12
Cython Features
▪ Compiling with Cython is fine, but it doesn't speed up our code
significantly
▪ We need to implement the C-features that Cython was designed
for:
▫ Static type declarations
▫ Typing Function Calls
▫ NumPy Arrays with Cython
▫ Compiler Directives

13
Static type declarations
▪ These allow Cython to step out of the dynamic nature of the
Python code and generate simpler and faster C code - sometimes
faster by orders of magnitude.
▪ This is often the simplest and quickest way to achieve significant
speedup, but the code can become more verbose and less
readable
▪ Types are declared with the cdef keyword

14
no cdef for function
Static type declarations arguments

def pi_montecarlo(n=1000): def pi_montecarlo(int n = 1000):

in_circle = 0 cdef int in_circle = 0, i


for i in range(n): cdef double x, y
x, y = random(), random() for i in range(n):
if x ** 2 + y ** 2 <= 1.0: x, y = random(), random()
in_circle += 1 if x ** 2 + y ** 2 <= 1.0:
in_circle += 1
return 4.0 * in_circle / n
return 4.0 * in_circle / n

cdef type
pi_estimator.py pi_estimator_cython.pyx Demo 2
15
Typing Function Calls
▪ As with 'typing' variables, you can also 'type' functions.
▪ Function calls in Python can be expensive, and can be even more expensive in
Cython as one might need to convert to and from Python objects to do the call.
▪ There are two ways in which to declare C-style functions in Cython:
▫ Declaring a C-type function - cdef
▫ Creation of a Python wrapper – cpdef
▪ Both are implemented the same way (same as declaring a variable)
▪ A side-effect of cdef is that the function is no longer available from Python-space, so
Python won't know how to call it
▪ Using cpdef allows the function to be available from both Python and Cython,
although cdef is more efficient

16
Typing Function Calls
cpdef double pi_montecarlo(int n = 1000):

cdef int in_circle = 0, i


cdef double x, y
for i in range(n):
x, y = random(), random()
if x ** 2 + y ** 2 <= 1.0:
in_circle += 1

return 4.0 * in_circle / n

Here we use cpdef as


we are calling the
function in the python
Demo 2
17 code
Pi Estimation Results (N=1000000)
0.25

0.2
As the function is
not being
0.15 repeatedly called,
typing it doesn’t
improve the
0.1
efficiency

0.05

0
Python Cython Cython w/ Cython w/
Static Types Static Types
(var) (var+func)
Time (sec)
18
When to add static types?
▪ For those of new to Cython and the concept of declaring types, there is a tendancy to 'type'
everything in sight. This reduces readability and flexibility and in certain situations, even slow
things down.
▪ It is also possible to kill performance by forgetting to 'type' a critical loop variable. Tools we can
use are profiling and annotation.
▪ Profiling is the first step of any optimisation effort and can tell you where the time is being spent.
By default, Cython code does not show up in profile produced by cProfile. Profiling can be enabled
by including in the first line # cython: profile=True
▪ Cython's annotation creates a HTML report of Cython and generated C code which can tell you
why your code is taking so long.
▪ To create the report pass the annotate=True parameter to cythonize() in the setup.py file (Note,
you may have to delete the c file and compile again to produce the HTML report).

19
Cython annotation

from distutils.core import setup


from Cython.Build import cythonize

setup(ext_modules=cythonize("pi_esti
mator_cython.pyx", annotate=True))

Demo 3
20
NumPy Arrays with Cython
▪ If you are dealing with numpy arrays, you can type the contents of the array to
speed up the runtime

cimport numpy as cnp


cdef cnp.ndarray[cnp.int_t, ndim=2] <array_name>

Datatype (for each type in


numpy there is a Number of dimensions
corresponding type with _t)

▪ Note the array still needs to be initialized e.g. <array_name> = np.zeros(2,3)

21
NumPy Arrays with Cython

import numpy as np
import numpy as np
cimport numpy as cnp

def powers_array(N):
def powers_array_cy(int N):
data = np.arange(N*N).reshape(N,N)
cdef cnp.ndarray[cnp.int_t, ndim=2] data
for i in range(N):
data = np.arange(N*N).reshape((N, N))
for j in range(N):
for i in range(N):
data[i,j] = i**j
for j in range(N):
return(data[2])
data[i,j] = i**j
return(data[2])

Demo 4
22
NumPy Arrays with Cython
▪ If using Cython-NumPy functionality we must modify the setup.py file

from distutils.core import setup


from Cython.Build import cythonize
import numpy

setup(ext_modules=cythonize("pow_cyt.pyx"),
include_dirs=[numpy.get_include()]
)

Demo 4
23
Compiler Directives
▪ These affect the code in a way to get the compiler to ignore things that it would usually look out for.
▪ boundscheck - If set to False, Cython is free to assume that indexing operations in the code will not cause
any IndexErrors to be raised
▪ wraparound - If set to False, Cython is allowed to neither check for nor correctly handle negative indices.
This can cause data corruption or segmentation faults if mishandled.

To implement

# cython: boundscheck=False cimport cython


# cython: wraparound=False @cython.boundscheck(False) # turns off
@cython.wraparound(False)

Header comment at the top of a .pyx Locally for specific functions (need
24 file (must appear before any code) cython module imported)
Putting Everything
Together
Conway’s Game of Life

25
Conway’s Game of Life
▪ 2D grid of cells
▪ Each cell is either alive (1) or dead (0)
▪ An alive cell will stay alive if it has 2 or 3
neighbours, else it will die
▪ A dead cell will become alive if it has 3
neighbours, else it stays dead
▪ The neighbours are the nearest 8 cells

26
Conway’s Game of Life
▪ Initialize as a cross
▪ Box size=300
▪ How long to update the
lattice 300 times?

27
Python
def update(lattice):
box_length = len(lattice) - 2
lattice_new = [[0 for _ in range(box_length + 2)] for _ in range(box_length + 2)]

for i in range(1, box_length + 1):


for j in range(1, box_length + 1):
lattice_new[i][j] = update_rule(lattice, i, j)
return lattice_new

def update_rule(lattice, i, j):


n_neigh = lattice[i + 1][j] + lattice[i][j + 1] + lattice[i + 1][j + 1] + \
lattice[i + 1][j - 1] + lattice[i - 1][j] + lattice[i][j - 1] + \
lattice[i - 1][j + 1] + lattice[i - 1][j - 1]

if (lattice[i][j] == 1) and (n_neigh in [2, 3]):


return 1
elif lattice[i][j] == 1:
return 0
elif (lattice[i][j] == 0) and (n_neigh == 3):
return 1
else:
return 0

Demo 5
28
Cython 1
def update(lattice):
box_length = len(lattice) - 2
lattice_new = [[0 for _ in range(box_length + 2)] for _ in range(box_length + 2)]

for i in range(1, box_length + 1):


for j in range(1, box_length + 1):
lattice_new[i][j] = update_rule(lattice, i, j)
return lattice_new

def update_rule(lattice, i, j):


n_neigh = lattice[i + 1][j] + lattice[i][j + 1] + lattice[i + 1][j + 1] + \
lattice[i + 1][j - 1] + lattice[i - 1][j] + lattice[i][j - 1] + \
lattice[i - 1][j + 1] + lattice[i - 1][j - 1]

if (lattice[i][j] == 1) and (n_neigh in [2, 3]):


return 1
elif lattice[i][j] == 1:
return 0
elif (lattice[i][j] == 0) and (n_neigh == 3):
return 1
else:
return 0

Demo 5
29
Cython 2
def update(lattice):
cdef int box_length = len(lattice) – 2
cdef int i, j
lattice_new = [[0 for _ in range(box_length + 2)] for _ in range(box_length + 2)]

for i in range(1, box_length + 1):


for j in range(1, box_length + 1):
lattice_new[i][j] = update_rule(lattice, i, j)
return lattice_new

def update_rule(lattice, int i, int j):


cdef int n_neigh
n_neigh = lattice[i + 1][j] + lattice[i][j + 1] + lattice[i + 1][j + 1] + \
lattice[i + 1][j - 1] + lattice[i - 1][j] + lattice[i][j - 1] + \
lattice[i - 1][j + 1] + lattice[i - 1][j - 1]

if (lattice[i][j] == 1) and (n_neigh in [2, 3]):


return 1
elif lattice[i][j] == 1:
return 0
elif (lattice[i][j] == 0) and (n_neigh == 3):
return 1
else:
return 0
Demo 5
30
Cython 3
def update(lattice):
cdef int box_length = len(lattice) – 2
cdef int i, j
lattice_new = [[0 for _ in range(box_length + 2)] for _ in range(box_length + 2)]

for i in range(1, box_length + 1):


for j in range(1, box_length + 1):
lattice_new[i][j] = update_rule(lattice, i, j)
return lattice_new

cdef int update_rule(lattice, int i, int j):


cdef int n_neigh
n_neigh = lattice[i + 1][j] + lattice[i][j + 1] + lattice[i + 1][j + 1] + \
lattice[i + 1][j - 1] + lattice[i - 1][j] + lattice[i][j - 1] + \
lattice[i - 1][j + 1] + lattice[i - 1][j - 1]

if (lattice[i][j] == 1) and (n_neigh in [2, 3]):


return 1
elif lattice[i][j] == 1:
return 0
elif (lattice[i][j] == 0) and (n_neigh == 3):
return 1
else:
return 0
Demo 5
31
Cython 4
# cython: boundscheck=False
# cython: wraparound=False

def update(lattice):
cdef int box_length = len(lattice) – 2
cdef int i, j
lattice_new = [[0 for _ in range(box_length + 2)] for _ in range(box_length + 2)]

for i in range(1, box_length + 1):


for j in range(1, box_length + 1):
lattice_new[i][j] = update_rule(lattice, i, j)
return lattice_new

cdef int update_rule(lattice, int i, int j):


cdef int n_neigh
n_neigh = lattice[i + 1][j] + lattice[i][j + 1] + lattice[i + 1][j + 1] + \
lattice[i + 1][j - 1] + lattice[i - 1][j] + lattice[i][j - 1] + \
lattice[i - 1][j + 1] + lattice[i - 1][j - 1]

if (lattice[i][j] == 1) and (n_neigh in [2, 3]):


return 1
elif lattice[i][j] == 1:
return 0
elif (lattice[i][j] == 0) and (n_neigh == 3):
return 1
else:
return 0
Demo 5
32
Results Time (sec)
16

14
Over a 6x speed up
12
Larger speed-ups are
10
possible, for the
8 Mandlebrot set
(exercise 4) the speed
6 up is ~250x
4

0
Python Cython 1 Cython 2 Cython 3 Cython 4
33 Time (sec)
Summary
34
Cython Summary
▪ To run Cython: ▪ To speed up Cython:

▫ Write code in .pyx ▫ Type variables

▫ Compile the .pyx with setup.py ▫ Type function calls

▫ python setup.py build_ext –inplace ▫ Use Cython’s numpy functionality

▫ Import module as normal ▫ Use Compiler directives

35

You might also like