I want to understand more about Cython's awesome typed-memoryviews and the memory layout indirect_contiguous.
According to the documentation indirect_contiguous is used when "the list of pointers is contiguous".
There's also an example usage:
# contiguous list of pointers to contiguous lists of ints
cdef int[::view.indirect_contiguous, ::1] b
So pls correct me if I'm wrong but I assume a "contiguous list of pointers to contiguous lists of ints" means something like the array created by the following c++ dummy-code:
// we want to create a 'contiguous list of pointers to contiguous lists of ints'
int** array;
// allocate row-pointers
// This is the 'contiguous list of pointers' related to the first dimension:
array = new int*[ROW_COUNT]
// allocate some rows, each row is a 'contiguous list of ints'
array[0] = new int[COL_COUNT]{1,2,3}
So if I understand correctly then in my Cython code it should be possible to get a memoryview from a int** like this:
cdef int** list_of_pointers = get_pointers()
cdef int[::view.indirect_contiguous, ::1] view = <int[:ROW_COUNT:view.indirect_contiguous,COL_COUNT:1]> list_of_pointers
But I get Compile-errors:
cdef int[::view.indirect_contiguous, ::1] view = <int[:ROW_COUNT:view.indirect_contiguous,:COL_COUNT:1]> list_of_pointers
^
------------------------------------------------------------
memview_test.pyx:76:116: Pointer base type does not match cython.array base type
what did I do wrong? Am I missing any casts or did I misunderstand the concept of indirect_contiguous?
Let's set the record straight: typed memory view can be only used with objects which implement buffer-protocol.
Raw C-pointers obviously don't implement the buffer-protocol. But you might ask, why something like the following quick&dirty code works:
Here, a pointer (
v) is used to construct a typed memory view (b). There is however more, going under the hood (as can be seen in the cythonized c-file):cython.view.array) is constructed, which wraps the raw pointer and can expose it via buffer-protocolYour understanding what
view.indirect_contiguousis used for is right - it is exactly what you desire. However, the problem isview.array, which just cannot handle this type of data-layout.view.indirectandview.indirect_contiguouscorrespond toPyBUF_INDIRECTin protocol-buffer parlance and for this the fieldsuboffsetsmust contain some meaningful values (i.e>=0for some dimensions). However, as can be see in the source-codeview.arraydoesn't have this member at all - there is no way it can represent the complex memory layout at all!Where does it leave us? As pointed out by @chrisb and @DavidW in your other question, you will have to implement a wrapper which can expose your data-structure via protocol-buffer.
There are data structures in Python, which use the indirect memory layout - most prominently the PIL-arrays. A good starting point to understand, how
suboffsetsare supposed to work is this piece of documenation:In your case
stridesandoffsetswould bestrides=[sizeof(int*), sizeof(int)](i.e.[8,4]on usualx86_64machines)offsets=[0,-1], i.e. only the first dimension is indirect.Getting the address of element
[x,y]would then happen as follows:A,pointeris set tobuf, let's assumeBUF.B,pointerbecomesBUF+x*8, and points to the location of the pointer to x-th row.suboffsets[0]>=0, we dereference the pointer in lineCand thus it shows to addressROW_X- the start of the x-th row.Bwe get the address of theyelement usingstrides, i.e.pointer=ROW_X+4*ysuboffset[1]<0), so no dereferencing is needed.pointerpoints to the desired address and is returned in lineD.FWIW, I have implemented a library which is able to export
int**and similar memory layouts via buffer protocol: https://github.com/realead/indirect_buffer.