python - Cython:了解具有间接连续内存布局的类型化内存 View

标签 python cython typed-memory-views

想进一步了解Cython的厉害typed-memoryviews和内存布局 indirect_contiguous

根据documentation 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** 获取内存 View :

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 的概念?

最佳答案

让我们澄清一下:类型化内存 View 只能用于实现了 buffer-protocol 的对象。 .

原始 C 指针显然没有实现缓冲区协议(protocol)。但是您可能会问,为什么像下面的快速代码这样的代码可以工作:

%%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)被用来构造一个类型化的内存 View (b)。然而,在幕后还有更多内容(如 cythonized c 文件中所示):

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

你的理解是什么view.indirect_contiguous用于是正确的 - 这正是您想要的。然而,问题是view.array ,它无法处理这种类型的数据布局。

view.indirectview.indirect_contiguous对应 PyBUF_INDIRECT 用 Protocol Buffer 的说法,为此字段 suboffsets 必须包含一些有意义的值(即某些维度的 >=0)。但是,正如在 source-code 中所见view.array根本没有这个成员——它根本无法表示复杂的内存布局!

它把我们留在哪里?正如@chrisb 和@DavidW 在您的其他问题中指出的那样,您将必须实现一个包装器,它可以通过 Protocol Buffer 公开您的数据结构。

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

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 .
  • 第一维度:
    • 排队B , pointer变成 BUF+x*8 , 并指向指向第 x 行的指针的位置。
    • 因为suboffsets[0]>=0 ,我们取消引用行 C 中的指针因此它显示地址 ROW_X - 第 x 行的开头。
  • 第二维度:
    • 排队B我们得到了 y 的地址元素使用 strides ,即 pointer=ROW_X+4*y
    • 第二维是直接的(由 suboffset[1]<0 发出信号),因此不需要取消引用。
  • 我们完成了,pointer指向所需的地址并在行 D 中返回.

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

关于python - Cython:了解具有间接连续内存布局的类型化内存 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53950020/

相关文章:

python - 无法计算两个日期之间的工作日?将 dtype ('<M8[ns]' ) 转换为 dtype ('<M8[D]' )?

python - 如何将列表中的相应元素分别添加到python字典键和值中

python - 在结构化数组中查找 header 名称的一部分的 header 索引

cython - 适用于 Android 的 Python : Error compiling Cython file

python - 在不复制的情况下将 C 数组绑定(bind)到 Numpy 数组

python-3.x - 使用整数的内存 View 索引 Cython 内存 View

python - 循环检查两个内存 View 的有效方法是什么?

python - 了解 Python Web 应用程序部署

python - LNK1181 : cannot open input file 'm.lib'

numpy - Cython Memoryview 中的数组广播