Cython类型的记忆视图:它们到底是什么?

时间:2016-05-25 08:48:44

标签: python arrays cython memoryview

Cython documentation很好地解释了它们允许的内容,如何声明它们以及如何使用它们。

然而,我仍然不清楚他们到底是什么。例如,来自numpy数组的简单赋值如下:

my_arr = np.empty(10, np.int32)
cdef int [:] new_arr = my_arr

可以更快地访问/分配my_arr

幕后发生了什么? Numpy应该已经以连续的方式在内存中分配元素了,那么内存视图的处理是什么?显然不是那么多,实际上numpy数组new_arr的memoryview赋值应该等同于

cdef np.ndarray[np.int32_t, ndim=1] new_arr = np.empty(10, np.int32)

速度方面。但是,内存视图被认为比numpy数组缓冲区更通用;你能举一个简单的例子来说明增加的概括'重要/有趣吗?

此外,如果我已经分配了一个指针以使事情尽可能快,那么将它投射到类型化的内存视图有什么好处? (这个问题的答案可能与上面的问题相同)

cdef int *my_arr = <int *> malloc(N * sizeof(int))
cdef int[:] new_arr = <int[:N]>my_arr

1 个答案:

答案 0 :(得分:20)

什么是记忆视图:

当你写一个函数时:

cdef double[:] a

最终得到一个__Pyx_memviewslice对象:

typedef struct {
  struct __pyx_memoryview_obj *memview;
  char *data;
  Py_ssize_t shape[8];
  Py_ssize_t strides[8];
  Py_ssize_t suboffsets[8];
} __Pyx_memviewslice;

memoryview包含一个C指针,它通常不会直接拥有某些数据。它还包含指向底层Python对象(struct __pyx_memoryview_obj *memview;)的指针。如果数据由Python对象拥有,则memview保存对该对象的引用,并确保只要内存视图存在,保存数据的Python对象就会保持活动状态。

指向原始数据的指针和如何索引它的信息(shapestridessuboffsets)的组合允许Cython使用原始数据指针进行索引编制和一些简单的C数学(非常有效)。 e.g:

x=a[0]

给出类似的东西:

(*((double *) ( /* dim=0 */ (__pyx_v_a.data + __pyx_t_2 * __pyx_v_a.strides[0]) )));

相反,如果您使用无类型对象并编写如下内容:

a = np.array([1,2,3]) # note no typedef
x = x[0]

索引完成如下:

__Pyx_GetItemInt(__pyx_v_a, 0, long, 1, __Pyx_PyInt_From_long, 0, 0, 1);

它本身扩展到一大堆Python C-api调用(所以很慢)。最终,它会调用a __getitem__方法。

与类型化的numpy数组相比:确实没有太大的区别。 如果您执行以下操作:

cdef np.ndarray[np.int32_t, ndim=1] new_arr

它实际上非常像一个内存视图,可以访问原始指针,速度应该非常相似。

使用内存视图的优点是您可以使用更广泛的数组类型(例如standard library array),因此您可以更灵活地调用函数的类型。这符合&#34; duck-typing&#34;的一般Python想法。 - 您的代码应该使用任何行为正确的参数(而不是检查类型)。

第二个(小)优势是你不需要numpy标头来构建你的模块。

第三个(可能更大)优势是可以在没有GIL的情况下初始化内存视图,而cdef np.ndarray s可以(http://docs.cython.org/src/userguide/memoryviews.html#comparison-to-the-old-buffer-support

内存视图的一个小缺点是它们似乎设置得稍慢。

与仅使用malloc ed int指针相比:

你没有获得任何速度优势(但你也不会得到太多的速度损失)。使用内存视图进行转换的次要优点是:

  1. 您可以编写可以在Python中或在Cython内部使用的函数:

    cpdef do_something_useful(double[:] x):
        # can be called from Python with any array type or from Cython
        # with something that's already a memoryview
        ....
    
  2. 你可以让Cython处理这种类型数组的内存释放,这可以简化生命未知事物的生命。请参阅http://docs.cython.org/src/userguide/memoryviews.html#cython-arrays,尤其是.callback_free_data

  3. 您可以将数据传递回python python代码(它将获得基础__pyx_memoryview_obj或类似的东西)。在这里要非常小心内存管理(即参见第2点!)。

  4. 您可以做的另一件事是处理定义为指针指针的2D数组(例如double**)。见http://docs.cython.org/src/userguide/memoryviews.html#specifying-more-general-memory-layouts。我通常不喜欢这种类型的数组,但是如果你已经使用了现有的C代码,那么你可以与它接口(并将它传递给Python,这样你的Python代码也可以使用它)。

相关问题