Cython: Transpose a memoryview

2019-07-28 04:07发布

问题:

Some background for the question:

I'm trying to optimise a custom neural network code. It relies heavily on loops and I decided to use cython to speed up the calculation.

I followed the usual online tips: Declare all local variables with appropriate cdefs and switch off boundscheck and nonecheck. This barely gave me 10% performance.

Well, my code relies on lots of class members. Therefore I decided to convert the entire class into a cdef class. Turns out that cython doesn't allow numpy ndarrays as types for class members. Instead one has to use memoryviews. Unfortunately the two types seem to be vastly incompatible.

I already ran into this problem: Cython memoryview transpose: Typeerror

To sum it up: You can store an np.ndarray in a memoryview. You can transpose it and store the returned array in a memview. But not if that memview is a class member. Then you have to create an intermediate memview, store the result in that and assign the intermediate memview to the class member.

Here's the code ( many thanks to DavidW)

def double[:,:,:,:] temporary_view_of_transpose

# temporary_view_of_transpose now "looks at" the memory allocated by transpose
# no square brackets!
temporary_view_of_transpose = out_image.transpose(1, 0, 2, 3)

# data is copied from temporary_view_of_transpose to self.y
self.y[...] = temporary_view_of_transpose # (remembering that self.y must be the correct shape before this assignment).

Now I've got a new problem. The code above is from the so-called "forward-pass". There is also a corresponding backward-pass, which does all the calculations backward (for analytical gradients).

This means that for the backward pass, I have to transpose the memoryview and store it in a numpy array:

cdef np.ndarray[DTYPE_t, ndim=4] d_out_image = self.d_y.transpose(1, 0, 2,3)

d_y has to be a class member, therefore it has to be a memoryview. Memoryviews don't allow transposing. They have a .T method, but that doesn't help me.

Actual Question:

  • How do I correctly store a numpy array as a class member of a cdef class?
  • If the answer is :"as a memoryview", how do I transpose a memoryview ?

回答1:

I think the best answer is "you store the numpy as an untyped python object"

cdef class C:
    cdef object array

    def example_function(self):
        # if you want to use the fast Cython array indexing in a function
        # you can do:
        cdef np.ndarray[np.float64_t,ndim=4] self_array = self.array
        # or
        cdef np.float64_t[:,:,:,:] self_array2 = self.array

        # note that neither of these are copies - they're references
        # to exactly the same array and so if you modify one it'll modify
        # self.array too

    def function2(self):
        return self.array.transpose(1,0,2,3) # works fine!

The small cost to doing it this way is that there's a bit of type-checking at the start of example_function to check that it is actually a 4D numpy array with the correct dtype. Provided you do a decent amount of work in the function that shouldn't matter.


As an alternative (if you decide you really want to store them as memoryviews) you could use np.asarray to convert it back to a numpy array without making a copy (i.e. they share data).

e.g.

cdef np.ndarray[DTYPE_t, ndim=4] d_out_image = np.asarray(self.d_y).transpose(1, 0, 2,3)