Cython:如何在没有NumPy数组的情况下创建memoryview?

12

我发现memory-views很方便和快速,因此在Cython中尽量避免创建NumPy数组,并使用给定数组的视图进行操作。然而,有时候不得不创建新的数组而不是修改现有数组。在高层函数中这并不明显,但在经常调用的子程序中就会有所体现。请考虑以下函数:

#@cython.profile(False)
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
cdef double [:] vec_eq(double [:] v1, int [:] v2, int cond):
    ''' Function output corresponds to v1[v2 == cond]'''
    cdef unsigned int n = v1.shape[0]
    cdef unsigned int n_ = 0
    # Size of array to create
    cdef size_t i
    for i in range(n):
        if v2[i] == cond:
            n_ += 1
    # Create array for selection
    cdef double [:] s = np.empty(n_, dtype=np_float) # Slow line
    # Copy selection to new array
    n_ = 0
    for i in range(n):
        if v2[i] == cond:
            s[n_] = v1[i]
            n_ += 1
    return s

性能分析告诉我,这里有一些可以提升的速度。

我可以尝试适应函数,因为有时候例如需要计算该向量的平均值,有时候需要求和。所以我可以重写它,以便进行求和或取平均值。但是,是否有一种方法可以"直接创建内存视图并动态定义大小,并带有非常小的开销"?比如,首先使用malloc等创建一个C缓冲区,然后在函数结束时将缓冲区转换为视图,传递指针和步长等。

编辑1: 也许对于简单的情况,像这样调整函数是可行的方法。我只添加了一个参数来实现求和/取平均值。这样我就不必创建数组,并且可以在易于处理的内部函数malloc中轻松处理。这样做不会更快,对吗?

# ...
cdef double vec_eq(double [:] v1, int [:] v2, int cond, opt=0):
    # additional option argument
    ''' Function output corresponds to v1[v2 == cond].sum() / .mean()'''
    cdef unsigned int n = v1.shape[0]
    cdef int n_ = 0
    # Size of array to create
    cdef Py_ssize_t i
    for i in prange(n, nogil=True):
        if v2[i] == cond:
            n_ += 1
    # Create array for selection
    cdef double s = 0
    cdef double * v3 = <double *> malloc(sizeof(double) * n_)
    if v3 == NULL:
        abort()
    # Copy selection to new array
    n_ = 0
    for i in range(n):
        if v2[i] == cond:
            v3[n_] = v1[i]
            n_ += 1
    # Do further computation here, according to option
    # Option 0 for the sum
    if opt == 0:
        for i in prange(n_, nogil=True):
            s += v3[i]
        free(v3)
        return s
    # Option 1 for the mean
    else:
        for i in prange(n_, nogil=True):
            s += v3[i]
        free(v3)
        return s / n_
    # Since in the end there is always only a single double value, 
    # the memory can be freed right here
4个回答

4

之前我不知道如何处理cpython数组,最后通过自己制作的“内存视图”(正如fabrizioM所建议的)解决了这个问题。我真没想到这样做会奏效。在紧密循环中创建新的np.array非常昂贵,因此这给我带来了显著的速度提升。由于我只需要一个一维数组,所以甚至不必担心步幅问题。但即使是对于高维数组,我认为这也可能奏效。

cdef class Vector:
    cdef double *data
    cdef public int n_ax0

    def __init__(Vector self, int n_ax0):
        self.data = <double*> malloc (sizeof(double) * n_ax0)
        self.n_ax0 = n_ax0

    def __dealloc__(Vector self):
        free(self.data)

...
#@cython.profile(False)
@cython.boundscheck(False)
cdef Vector my_vec_func(double [:, ::1] a, int [:] v, int cond, int opt):
    # function returning a Vector, which can be hopefully freed by del Vector
    cdef int vecsize
    cdef size_t i
    # defs..
    # more stuff...
    vecsize = n
    cdef Vector v = Vector(vecsize)

    for i in range(vecsize):
        # computation
        v[i] = ...

    return v

...
vec = my_vec_func(...
ptr_to_data = vec.data
length_of_vec = vec.n_ax0

0

根据http://docs.cython.org/src/userguide/memoryviews.html,可以通过以下方式为Cython内存视图分配内存:

cimport cython
cdef type [:] cview = cython.view.array(size = size, 
              itemsize = sizeof(type), format = "type", allocate_buffer = True)

或通过

from libc.stdlib import malloc, free
cdef type [:] cview = <type[:size]> malloc(sizeof(type)*size)

两种情况都可以工作,但是在第一种情况下,如果引入自己的类型(ctypedef some mytype),就会出现问题,因为没有合适的格式。

在第二种情况下,存在内存释放的问题。

根据手册,应该按照以下方式工作:

cview.callback_memory_free = free

绑定函数将内存释放给memoryview,但是此代码无法编译。


callback_memory_free 实际上是 cython.view.array 的一个属性,而不是视图的属性。 - cfh

0

阅读线程,我可以使用malloc - 我已经在函数中处理过了,但是当返回指针时我不知道如何处理(我必须调整一些代码)- 或者我使用cpython.array。这使我想到了这个线程。有没有更多关于如何在cython中使用cpython数组的信息? - embert
1
请查看我的评论,以及我的建议。(也就是说,“这些基准测试真的很误导人”) - Veedrac

0
受到@embert的“memory view”向量类型的启发,这里提供了一个类似的矩阵“memory view”类型,用于存储二维数组数据。
# Importing necessary libraries
from libc.stdlib cimport malloc, free  # Importing memory allocation and deallocation functions

# Defining a Cython class called Matrix
cdef class Matrix:
    cdef double** data  # A 2D array to store the matrix data
    cdef int n, m  # Variables to store the dimensions of the matrix

    # Constructor for the Matrix class
    def __init__(Matrix self, int n, int m):
        self.data = <double **> malloc(n * m * sizeof(double))  # Allocating memory for the matrix data
        self.n = n  # Storing the number of rows
        self.m = m  # Storing the number of columns

    # Destructor for the Matrix class
    def __dealloc__(Matrix self):
        free(self.data)  # Deallocating the memory for the matrix data

    # Method to retrieve a value from the matrix
    def __getitem__(Matrix self, (int, int) ind):
        return self.data[ind[0]][ind[1]]

    # Method to set a value in the matrix
    def __setitem__(Matrix self, (int, int) ind, double value):
        self.data[ind[0]][ind[1]] = value

# Example usage

# Creating a Matrix object with dimensions 2x3
mat = Matrix(2, 3)

# Setting values in the matrix
mat[0, 1] = 4.885
mat[2, 1] = 75.74

# Retrieving a value from the matrix and printing it
print(mat[0, 1])


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接