The Cython documentation explains very well what they allow for, how you can declare them, and how to use them.
However, it is still not clear to me what they really are. For example, a simple assignment from a numpy array like this:
my_arr = np.empty(10, np.int32)
cdef int [:] new_arr = my_arr
can make the accessing/assignment of my_arr
faster.
What is it happening behind the scenes? Numpy should already allocate the elements in memory in a contiguous fashion, so what's the deal with memoryviews? Apparently not that much, in fact the memoryview assignment of the numpy array new_arr
should be equivalent to
cdef np.ndarray[np.int32_t, ndim=1] new_arr = np.empty(10, np.int32)
in terms of speed. However, memoryviews are considered more general than numpy array buffer; could you make a simple example in which the added 'generalization' is important/interesting?
Furthermore, if I have already allocated a pointer in order to make things as fast as possible, what is the advantage of casting it to a typed memoryview? (the answer to this question might be the same of the one above)
cdef int *my_arr = <int *> malloc(N * sizeof(int))
cdef int[:] new_arr = <int[:N]>my_arr
What is a memoryview:
When you write in a function:
you end up with a
__Pyx_memviewslice
object:The memoryview contains a C pointer some some data which it (usually) doesn't directly own. It also contains a pointer to an underlying Python object (
struct __pyx_memoryview_obj *memview;
). If the data is owned by a Python object thenmemview
holds a reference to that and ensures the Python object that holds the data is kept alive as long as the memoryview is around.The combination of the pointer to the raw data, and information of how to index it (
shape
,strides
andsuboffsets
) allows Cython to do indexing the using the raw data pointers and some simple C maths (which is very efficient). e.g.:gives something like:
In contrast, if you work with untyped objects and write something like:
the indexing is done as:
which itself expands to a whole bunch of Python C-api calls (so is slow). Ultimately it calls
a
's__getitem__
method.Compared to typed numpy arrays: there really isn't a huge difference. If you do something like:
it works practically very like a memoryview, with access to raw pointers and the speed should be very similar.
The advantage to using memoryviews is that you can use a wider range of array types with them (such as the standard library array), so you're more flexible about the types your functions can be called with. This fits in with the general Python idea of "duck-typing" - that your code should work with any parameter that behaves the right way (rather than checking the type).
A second (small) advantage is that you don't need the numpy headers to build your module.
A third (possibly larger) advantage is that memoryviews can be initialised without the GIL while
cdef np.ndarray
s can't (http://docs.cython.org/src/userguide/memoryviews.html#comparison-to-the-old-buffer-support)A slight disadvantage to memoryviews is that they seem to be slightly slower to set up.
Compared to just using
malloc
ed int pointers:You won't get any speed advantage (but neither will you get too much speed loss). The minor advantages of converting using a memoryview are:
You can write functions that can be used either from Python or internally within Cython:
You can let Cython handle the freeing of memory for this type of array, which could simplify your life for things that have an unknown lifetime. See http://docs.cython.org/src/userguide/memoryviews.html#cython-arrays and especially
.callback_free_data
.You can pass your data back to python python code (it'll get the underlying
__pyx_memoryview_obj
or something similar). Be very careful of memory management here (i.e. see point 2!).The other thing you can do is handle things like 2D arrays defined as pointer to pointer (e.g.
double**
). See http://docs.cython.org/src/userguide/memoryviews.html#specifying-more-general-memory-layouts. I generally don't like this type of array, but if you have existing C code that already uses if then you can interface with that (and pass it back to Python so your Python code can also use it).