【问题标题】:Cython typed memoryviews: what they really are?Cython 键入的内存视图:它们到底是什么?
【发布时间】:2023-03-19 10:10:01
【问题描述】:

Cython documentation 很好地解释了它们允许什么、如何声明它们以及如何使用它们。

但是,我仍然不清楚它们到底是什么。例如,来自 numpy 数组的简单赋值是这样的:

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

可以更快地访问/分配my_arr

幕后发生了什么? Numpy 应该已经以连续的方式分配内存中的元素,那么 memoryviews 是怎么回事呢?貌似没那么多,其实numpy数组new_arr的memoryview赋值应该相当于

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

在速度方面。但是,memoryviews 被认为比 numpy 数组缓冲区更通用;你能举一个简单的例子,其中添加的“概括”很重要/有趣吗?

此外,如果我已经分配了一个指针以使事情尽可能快,那么将其转换为类型化的内存视图有什么好处? (这个问题的答案可能和上面的一样)

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

【问题讨论】:

  • 文档的第一行 Typed memoryviews allow efficient access to memory buffers, such as those underlying NumPy arrays, without incurring any Python overhead. 在我看来 memoryview 只是 cython's 自己的一组用于访问缓冲区的 c 函数,绕过了 numpy 函数。它不会比直接c 样式访问快,但可能更易于使用。
  • 如果你看一下编译后的 CPP,你可以看到 memoryview 是如何声明和使用的。

标签: python arrays cython memoryview


【解决方案1】:

什么是记忆视图:

当你写在一个函数中时:

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 对象就会保持活动状态。

指向原始数据的指针和如何索引它的信息(shapestridessuboffsets)的组合允许 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 进行转换的次要优势是:

  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. 您可以做的另一件事是处理定义为指针指针的二维数组(例如double**)。见http://docs.cython.org/src/userguide/memoryviews.html#specifying-more-general-memory-layouts。我一般不喜欢这种类型的数组,但是如果你有已经使用 if 的现有 C 代码,那么你可以与它进行交互(并将其传递回 Python,以便你的 Python 代码也可以使用它)。

【讨论】:

    猜你喜欢
    • 2014-11-29
    • 2013-07-02
    • 2023-03-14
    • 2019-09-20
    • 2015-02-12
    • 2011-09-08
    • 1970-01-01
    • 1970-01-01
    • 2016-07-15
    相关资源
    最近更新 更多