hi there serdar yagilov here for infoworld in this episode of smartpython we're going
to look at the basics of integrating cython with numpy now if you've been following my
previous videos you know by now that cython is a tool for taking type annotated python code
and compiling it to c this way slow math operations in python can be optimized
and you can get speed boosts of many orders of magnitude
numpy as you might also know by now is a popular python library for performing fast array math
so you might ask if we have numpy which is already fast why cython well the answer is simple
sometimes we have to perform manual operations on an array that can't be done directly in numpy and
normally we write c or fortran code to do that but with cython we can write that code in python
get it running if slowly at first and then transform it incrementally into fast c code
let's start with an example that i've taken with some modification from cython's own documentation
this function takes in two numpy arrays processes results from both of them and
combines those results into a third array and in order to do this we have to iterate over
the arrays element by element so to that end this loop here is where we're going to be spending the
vast majority of our time in the code now if we run this code and we see how long it takes we'll
see that without doing any real optimizations at all our code will complete in about five seconds
that's not so bad for a one-off for something that we're just doing intermittently but if we
were processing tens of thousands of arrays like this that would constitute a massive bottleneck
so to that end let's take a look at the same code except rewritten in cython now again if you see my
earlier videos and articles on cython you'll know that it's basically python with some custom syntax
and that syntax is mainly used to declare types for objects so the python compiler can then
optimize for the use of those types for instance in our clip function we define all of the types as
integers and we define that the function itself returns an integer we also use cdf to indicate
that this function is pure c it doesn't get called directly from any python code only from within
this python module so that's another optimization that we can perform the compute function has some
more psython specific type declarations most of which revolve around providing information
about numpy array types for instance in the function signature we indicate that we want
to take in two arrays each is an area of integers with two dimensions and this peculiar syntax we
have here is used to say in python that we want to get a memory view from this two-dimensional array
a memory view is a python feature where we can access the underlying data
in an array directly and this includes numpy arrays this way we don't have to use numpy's
methods to get or set the data as we would in plain python we can just work directly with it
by the way this thing at the end here this colon colon one this is a way of indicating how the data
in the array is strided or organized internally to the array and different striding representations
are how we talk about whether or not we want to use a c style array or a fortran style array
depending on the work that we're on the work that we're doing one other useful thing that we can do
with cython is import numpy's methods and use them directly if we need to for instance here
with result which is going to be our output array we create that array in exactly the same way
that we did our original python code but below that we create this other variable result view
that's a memory view into the data for result and we create that because it's going to be
faster to work with the memory view than with the result object the rest of this code
as you can see is almost exactly the same it's just that it's now iterating in c and
manipulating the underlying data directly instead of working through numpy's python interfaces
and at the end by the way we are returning result which is this new numpy array object that we
created and populated but we don't return the memory view itself because that's for
our own internal use in python only python would have no idea what to do with that now
if we compile this code and use it in place of our original code we're going to see a dramatic speed
up what used to take us as much as five seconds now runs in barely 1 100 of a second this hot loop
inside the code the part where we would have to iterate over every single element individually
has been translated into c and so we do all of our work directly on the data in the array in c
instead of through numpy's python interfaces for that data that's the secret sauce right
here we can write code that operates directly on that data inside the numpy object without the
overhead of going back and forth through numpy's python interfaces and we can do all of that at c
speeds now if we look at the code analysis for psython that was generated for this module
we can see some more information about this how the speedup works these code analyses by
the way they tell us how much of the code has been translated into pure c and how much is
still dependent on the python runtime the parts in yellow indicate the bits of the module that still
rely on the python api and so are comparatively slower but the two most important parts of the
program the clip function and the iteration over the array those are all pure c now generally
when you're using cython integrations with numpy you want to do three things first as noted here
you get the fastest results with numpy arrays when you use a memory view you can speed things up even
further by disabling boundary checking on memory views but that comes at a cost for instance you'll
notice i have these decorators here and what these do is that they disable boundary checking and
negative index checking in our code for arrays and for memory views which speeds things up further
we don't have to perform those checks at every access operation but the thing is that we're
not doing these access operations blindly we're doing a little pre-calculation here
to make sure that our area accesses never go out of bounds the range objects are based on the
sizes of the arrays in real time so we're never accessing something from out of bounds by mistake
so if you do opt for speed in this way you want to make sure that you don't accidentally go
out of bounds with your array accessing or you will have a segment or memory fault at runtime
the second thing you want to do is move only the code that needs to be in cython into cython this
function you'll notice has very little in it apart from this hot loop that we're optimizing all the
other stuff the setup and the tear down all of the things that are not actually performance sensitive
those cannons should be done in the python side for one it'll mean less hassle working
with psython generally and two it's also because that way you don't have to recompile your cython
modules every time you make a trivial change that doesn't really affect performance anyway
third piece of advice and this is good advice for psython generally you want to keep the number of
function calls made between the python side and your cython modules to a minimum you'll notice
that in this example we're making exactly one call into our compute function from the python
side and when we do this we're sending over all the things that we need to perform the work
don't perform iteration over a numpy area on the python side if you're putting if you're
using psython somewhere in the equation because that's just a total waste of effort just give
the psython side everything that it needs to do to perform the work and stand back
so in future videos i'm going to be exploring more ways that cython and numpy can work
together for instance you could use the openmp system as part of the module build process
so that you could perform multi-threaded operations within a cython module this is
all more advanced stuff so again i'm saving it for future videos but if you like this video please
leave a comment below don't forget to subscribe to the tech talk channel on youtube and for more
smart python and more dev with serdar be sure to follow me on facebook youtube and infoworld.com
you