3

我想了解更多关于 Cython 令人敬畏的typed-memoryviews和 memory layout 的信息indirect_contiguous

根据文档 indirect_contiguous,当“指针列表是连续的”时使用。

还有一个示例用法:

# contiguous list of pointers to contiguous lists of ints
cdef int[::view.indirect_contiguous, ::1] b

因此,如果我错了,请纠正我,但我假设“指向连续整数列表的指针的连续列表”意味着类似于由以下 c++ 虚拟代码创建的数组:

// 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}

因此,如果我理解正确,那么在我的 Cython 代码中应该可以从int**这样的代码中获取内存视图:

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

但我得到编译错误:

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

我做错什么了?我错过了任何演员还是我误解了indirect_contiguous的概念?

4

1 回答 1

3

让我们直截了当地说:类型化的内存视图只能与实现buffer-protocol的对象一起使用。

原始 C 指针显然没有实现缓冲区协议。但是你可能会问,为什么像下面的快速&脏代码这样的东西有效:

%%cython    
from libc.stdlib cimport calloc
def f():
    cdef int* v=<int *>calloc(4, sizeof(int))
    cdef int[:] b = <int[:4]>v
    return b[0] # leaks memory, so what?

在这里,指针 ( v) 用于构造类型化的内存视图 ( b)。然而,还有更多,在引擎盖下(可以在 cythonized c 文件中看到):

  • 构造了一个cython 数组(即cython.view.array),它包装了原始指针并可以通过缓冲区协议公开它
  • 该数组用于创建类型化的内存视图。

您对用途的理解view.indirect_contiguous是正确的——这正是您想要的。但是,问题是view.array,它无法处理这种类型的数据布局。

view.indirectview.indirect_contiguous对应 PyBUF_INDIRECT于协议缓冲区的说法,为此,该字段suboffsets必须包含一些有意义的值(即>=0对于某些维度)。但是,从源代码 中可以看出view.array根本没有这个成员——它根本无法代表复杂的内存布局!

它把我们留在哪里?正如@chrisb 和@DavidW 在您的另一个问题中指出的那样,您必须实现一个包装器,该包装器可以通过协议缓冲区公开您的数据结构。

Python中有一些使用间接内存布局的数据结构——最突出的是PIL数组。一个很好的起点来理解,suboffsets应该如何工作是这个文档

void *get_item_pointer(int ndim, void *buf, Py_ssize_t *strides,
                       Py_ssize_t *suboffsets, Py_ssize_t *indices) {
    char *pointer = (char*)buf;    // A
    int i;
    for (i = 0; i < ndim; i++) {
        pointer += strides[i] * indices[i]; // B
        if (suboffsets[i] >=0 ) {
            pointer = *((char**)pointer) + suboffsets[i];  // C
        }
    }
    return (void*)pointer;  // D
}

在你的情况下stridesoffsets将是

  • strides=[sizeof(int*), sizeof(int)](即[8,4]在普通x86_64机器上)
  • offsets=[0,-1],即只有第一个维度是间接的。

获取元素的地址[x,y]将发生如下:

  • 在该行A中,pointer设置为buf,让我们假设BUF
  • 第一个维度:
    • 在 line 中Bpointer变为BUF+x*8,并指向指向第 x 行的指针的位置。
    • 因为suboffsets[0]>=0,我们取消引用行中的指针,C因此它显示为地址ROW_X- 第 x 行的开始。
  • 第二个维度:
    • 在行中,B我们使用 获取y元素的地址strides,即pointer=ROW_X+4*y
    • 第二维是直接的(由 表示suboffset[1]<0),因此不需要取消引用。
  • 我们完成了,pointer指向所需的地址并在 line 中返回D

FWIW,我已经实现了一个库,它能够int**通过缓冲区协议导出和类似的内存布局:https ://github.com/reallead/indirect_buffer 。

于 2018-12-27T22:32:51.900 回答