什么是记忆视图:
当你写在一个函数中时:
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 持有对该对象的引用,并确保只要 memoryview 存在,保存数据的 Python 对象就会保持活动状态。
指向原始数据的指针和如何索引它的信息(shape、strides 和 suboffsets)的组合允许 Cython 使用原始数据指针和一些简单的 C 数学(这是非常有效的)。例如:
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
它实际上非常像内存视图,可以访问原始指针并且速度应该非常相似。
使用 memoryviews 的优点是您可以使用更广泛的数组类型(例如standard library array),因此您可以更灵活地使用您的函数可以调用的类型。这符合 Python 的“鸭子类型”的一般理念——您的代码应该使用任何行为正确的参数(而不是检查类型)。
第二个(小)优势是您不需要 numpy 标头来构建您的模块。
第三个(可能更大)优势是内存视图可以在没有 GIL 的情况下初始化,而 cdef np.ndarrays 不能 (http://docs.cython.org/src/userguide/memoryviews.html#comparison-to-the-old-buffer-support)
memoryviews 的一个小缺点是它们的设置速度似乎稍慢。
与仅使用 malloced int 指针相比:
您不会获得任何速度优势(但您也不会获得太多的速度损失)。使用 memoryview 进行转换的次要优势是:
-
您可以编写可从 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 点!)。
-
您可以做的另一件事是处理定义为指针指针的二维数组(例如double**)。见http://docs.cython.org/src/userguide/memoryviews.html#specifying-more-general-memory-layouts。我一般不喜欢这种类型的数组,但是如果你有已经使用 if 的现有 C 代码,那么你可以与它进行交互(并将其传递回 Python,以便你的 Python 代码也可以使用它)。