来自 Cython 中动态数组的 2D MemoryView

2D MemoryView from dynamic arrays in Cython

我知道这个 ,但我一直在寻找一种更简单的方法来从 C 数组生成二维内存视图。由于我是 C 和 Cython 新手,有人可以解释一下为什么像

cdef int[:, :] get_zeros(int d):
    # get 2-row array of zeros with d as second dimension
    cdef int i
    cdef int *arr = <int *> malloc(sizeof(int) * d)
    for i in range(d):
        arr[i] = 0
    cdef int[:, :] arr_view
    arr_view[0, :] = <int[:d]>arr
    arr_view[1, :] = <int[:d]>arr
    return arr_view

行不通?

编译时出现 Cannot assign type 'int[::1]' to 'int' 错误。这是否意味着 2d memview 被第一个分配给 1d 的语句折叠,或者是因为 memoryviews 需要连续的块等?

"explain why something [...] won't work" 显然很难做到,因为最终这只是一个设计决定,可以采取不同的方式。但是:

Cython 内存视图设计得非常愚蠢。他们所做的只是提供一些很好的语法来访问实现the Python buffer protocol的东西的内存,然后有一点点额外的语法让你做一些事情,比如获取指针的一维内存视图。

此外,memoryview 作为一个整体 包装了一些东西。当您创建 cdef int[:, :] arr_view 时它是无效的,直到您创建 arr_view = something。尝试分配给它的一部分是胡说八道,因为(a)它会将分配委托给它使用缓冲协议包装的东西,以及(b)分配的确切方式将取决于您包装的缓冲协议格式.如果包装 "indirect" 缓冲区协议对象,您所做的 可能 有效,但如果包装连续数组,则没有任何意义。由于 arr_view 可能正在包装,因此 Cython 编译器必须将其视为错误。

实现缓冲协议,因此是实现这种数组的正确方法。你试图做的是采用额外的语法,从指针中给出一维内存视图,并将其强制为二维内存视图的一部分,模糊地希望这可能有效。这需要很多逻辑,远远超出了 Cython 内存视图的设计范围。


可能还有几点值得一提:

  • 指针的内存视图不处理指针的释放(因为它们几乎不可能再次猜测您想要什么)。你必须处理这个逻辑。如果有效,您当前的设计会泄漏内存。在设计中,您链接到包装 class 可以在 __dealloc__ 中实现这一点(尽管它没有在该答案中显示)因此更好。

  • 我个人的看法是"ragged arrays"(指向指针的二维数组)很糟糕。它们需要大量的分配和释放。有很多机会对它们进行半初始化。访问它们需要几个间接级别,因此速度很慢。唯一适合他们的是他们在 C 中提供了 arr[idx1][idx2] 语法。总的来说,我更喜欢 Numpy 分配一维数组并使用 shape/strides 计算索引位置的方法。 (显然,如果您要包装现有的库,那么您可能不是您的选择...)

除了@DavidW 提供的精彩回答外,我还想补充一些信息。在您包含的代码中,我看到您正在 malloc-ing 一个整数数组,然后将 for 循环中的内容归零。一个更方便的实现方法是使用 C 的 calloc 函数,它保证了一个指向归零内存的指针并且之后不需要 for 循环。

此外,您可以创建一个指向 "array" 数据的单个 int *,该数据被调用到总大小为 2 * d * sizeof(int)。这将确保两个 "rows" 数据彼此连续,而不是分开和参差不齐。然后可以将其直接转换为二维内存视图。

正如评论中所承诺的那样,转换代码可能如下所示(包括 calloc 使用):

cdef int[:, :] get_zeros(int d):    
    cdef int *arr = <int *>calloc(2 * d, sizeof(int))
    cdef int[:, :] arr_view = <int[:2, :d]>arr
    return arr_view

cython 的 mem.pxd 模块中似乎也有一个 calloc equivalent in the python c-api per the docs if you want to try it out. However, it does not appear to be wrapped,这就是您可能找不到它的原因。您可以在代码中声明一个类似的 extern 块,以像 link.

中包含的其他函数一样包装它

这里是 bonus link 如果您想了解更多关于编写分配器以在您走预分配路线时从大块中分配内存的信息(即 PyMem_* 函数可能在场景,但更可调,并且在您的特定用例控制之下)。