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
答案 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对象就会保持活动状态。
指向原始数据的指针和如何索引它的信息(shape
,strides
和suboffsets
)的组合允许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指针相比:
你没有获得任何速度优势(但你也不会得到太多的速度损失)。使用内存视图进行转换的次要优点是:
您可以编写可以在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
....
你可以让Cython处理这种类型数组的内存释放,这可以简化生命未知事物的生命。请参阅http://docs.cython.org/src/userguide/memoryviews.html#cython-arrays,尤其是.callback_free_data
。
您可以将数据传递回python python代码(它将获得基础__pyx_memoryview_obj
或类似的东西)。在这里要非常小心内存管理(即参见第2点!)。
您可以做的另一件事是处理定义为指针指针的2D数组(例如double**
)。见http://docs.cython.org/src/userguide/memoryviews.html#specifying-more-general-memory-layouts。我通常不喜欢这种类型的数组,但是如果你已经使用了现有的C代码,那么你可以与它接口(并将它传递给Python,这样你的Python代码也可以使用它)。