首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >强制NumPy ndarray在Cython中获得其内存的所有权

强制NumPy ndarray在Cython中获得其内存的所有权
EN

Stack Overflow用户
提问于 2014-05-26 15:00:15
回答 3查看 4.6K关注 0票数 15

这个答案是:“我能强迫一只矮胖的精灵拥有它的记忆吗?”之后,我尝试通过Cython的NumPy包装器使用Python函数PyArray_ENABLEFLAGS,发现它没有公开。

下面是手动公开它的尝试(这只是一个再现失败的最小示例)

代码语言:javascript
运行
复制
from libc.stdlib cimport malloc
import numpy as np
cimport numpy as np

np.import_array()

ctypedef np.int32_t DTYPE_t

cdef extern from "numpy/ndarraytypes.h":
    void PyArray_ENABLEFLAGS(np.PyArrayObject *arr, int flags)

def test():
    cdef int N = 1000

    cdef DTYPE_t *data = <DTYPE_t *>malloc(N * sizeof(DTYPE_t))
    cdef np.ndarray[DTYPE_t, ndim=1] arr = np.PyArray_SimpleNewFromData(1, &N, np.NPY_INT32, data)
    PyArray_ENABLEFLAGS(arr, np.NPY_ARRAY_OWNDATA)

编译错误失败:

代码语言:javascript
运行
复制
Error compiling Cython file:
------------------------------------------------------------
...
def test():
    cdef int N = 1000

    cdef DTYPE_t *data = <DTYPE_t *>malloc(N * sizeof(DTYPE_t))
    cdef np.ndarray[DTYPE_t, ndim=1] arr = np.PyArray_SimpleNewFromData(1, &N, np.NPY_INT32, data)
    PyArray_ENABLEFLAGS(arr, np.NPY_ARRAY_OWNDATA)
                          ^
------------------------------------------------------------

/tmp/test.pyx:19:27: Cannot convert Python object to 'PyArrayObject *'

我的问题:在这种情况下,这是正确的方法吗?如果是的话,我做错了什么?如果不是,我如何强迫NumPy获得Cython的所有权,而不深入到C扩展模块呢?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2014-05-26 15:40:08

您只是在接口定义中有一些小错误。以下几点对我有用:

代码语言:javascript
运行
复制
from libc.stdlib cimport malloc
import numpy as np
cimport numpy as np

np.import_array()

ctypedef np.int32_t DTYPE_t

cdef extern from "numpy/arrayobject.h":
    void PyArray_ENABLEFLAGS(np.ndarray arr, int flags)

cdef data_to_numpy_array_with_spec(void * ptr, np.npy_intp N, int t):
    cdef np.ndarray[DTYPE_t, ndim=1] arr = np.PyArray_SimpleNewFromData(1, &N, t, ptr)
    PyArray_ENABLEFLAGS(arr, np.NPY_OWNDATA)
    return arr

def test():
    N = 1000

    cdef DTYPE_t *data = <DTYPE_t *>malloc(N * sizeof(DTYPE_t))
    arr = data_to_numpy_array_with_spec(data, N, np.NPY_INT32)
    return arr

这是我的setup.py文件:

代码语言:javascript
运行
复制
from distutils.core import setup, Extension
from Cython.Distutils import build_ext
ext_modules = [Extension("_owndata", ["owndata.pyx"])]
setup(cmdclass={'build_ext': build_ext}, ext_modules=ext_modules)

使用python setup.py build_ext --inplace构建。然后验证数据是否实际拥有:

代码语言:javascript
运行
复制
import _owndata
arr = _owndata.test()
print arr.flags

在其他方面,您应该看到OWNDATA : True

是的,这绝对是处理这个问题的正确方法,因为numpy.pxd在将所有其他函数导出到Cython时都做了完全相同的事情。

票数 19
EN

Stack Overflow用户

发布于 2019-05-02 20:35:30

@Stefan解适用于大多数场景,但有些脆弱。Numpy使用PyDataMem_NEW/PyDataMem_FREE进行内存管理,这是一个实现细节,这些调用映射到通常的malloc/free +某些内存跟踪(我不知道Stefan的解决方案对内存跟踪有什么影响,至少它似乎不会崩溃)。

还有更复杂的情况,如numpy库中的free没有在cython代码中使用与malloc相同的内存分配器(针对不同的运行时链接,例如在github-发行所以-邮寄中)。

传递/管理数据所有权的正确工具是PyArray_SetBaseObject.

首先,我们需要一个python对象,它负责释放内存。我在这里使用的是一个自制的cdef类(主要是因为日志/演示),但显然还有其他可能性:

代码语言:javascript
运行
复制
%%cython
from libc.stdlib cimport free

cdef class MemoryNanny:
    cdef void* ptr # set to NULL by "constructor"
    def __dealloc__(self):
        print("freeing ptr=", <unsigned long long>(self.ptr)) #just for debugging
        free(self.ptr)
        
    @staticmethod
    cdef create(void* ptr):
        cdef MemoryNanny result = MemoryNanny()
        result.ptr = ptr
        print("nanny for ptr=", <unsigned long long>(result.ptr)) #just for debugging
        return result

 ...

现在,我们使用一个MemoryNanny-object作为内存的前哨,一旦父-numpy-数组被销毁,它就会被释放。代码有点尴尬,因为PyArray_SetBaseObject窃取引用,Cython不会自动处理该引用:

代码语言:javascript
运行
复制
%%cython
...
from cpython.object cimport PyObject
from cpython.ref cimport Py_INCREF

cimport numpy as np

#needed to initialize PyArray_API in order to be able to use it
np.import_array()


cdef extern from "numpy/arrayobject.h":
    # a little bit awkward: the reference to obj will be stolen
    # using PyObject*  to signal that Cython cannot handle it automatically
    int PyArray_SetBaseObject(np.ndarray arr, PyObject *obj) except -1 # -1 means there was an error
          
cdef array_from_ptr(void * ptr, np.npy_intp N, int np_type):
    cdef np.ndarray arr = np.PyArray_SimpleNewFromData(1, &N, np_type, ptr)
    nanny = MemoryNanny.create(ptr)
    Py_INCREF(nanny) # a reference will get stolen, so prepare nanny
    PyArray_SetBaseObject(arr, <PyObject*>nanny) 
    return arr
...

下面是一个例子,如何调用此功能:

代码语言:javascript
运行
复制
%%cython
...
from libc.stdlib cimport malloc
def create():
    cdef double *ptr=<double*>malloc(sizeof(double)*8);
    ptr[0]=42.0
    return array_from_ptr(ptr, 8, np.NPY_FLOAT64)

可用于以下方面:

代码语言:javascript
运行
复制
>>> m =  create()
nanny for ptr= 94339864945184
>>> m.flags
...
OWNDATA : False
...
>>> m[0]
42.0
>>> del m
freeing ptr= 94339864945184

与预期的结果/输出。

注意:结果数组并不真正拥有数据(即标志返回OWNDATA : False),因为内存是由内存保姆拥有的,但结果是一样的:一旦删除数组,内存就会被释放(因为不再有对保姆的引用)。

MemoryNanny不需要保护原始的C指针.它可以是其他任何东西,例如,一个std::vector

代码语言:javascript
运行
复制
%%cython -+
from libcpp.vector cimport vector
cdef class VectorNanny:
    #automatically default initialized/destructed by Cython:
    cdef vector[double] vec 
    @staticmethod
    cdef create(vector[double]& vec):
        cdef VectorNanny result = VectorNanny()
        result.vec.swap(vec) # swap and not copy
        return result
   
# for testing:
def create_vector(int N):
    cdef vector[double] vec;
    vec.resize(N, 2.0)
    return VectorNanny.create(vec)

以下测试显示,保姆工作:

代码语言:javascript
运行
复制
nanny=create_vector(10**8) # top shows additional 800MB memory are used
del nanny                  # top shows, this additional memory is no longer used.
票数 7
EN

Stack Overflow用户

发布于 2020-03-25 19:26:27

最新的Cython版本允许您使用最少的语法,尽管比所建议的较低级别的解决方案开销稍大。

numpy_array = np.asarray(<np.int32_t[:10, :10]> my_pointer)

https://cython.readthedocs.io/en/latest/src/userguide/memoryviews.html#coercion-to-numpy

这本身并不能传递所有权。

特别要注意的是,使用此调用通过array_cwrapper生成Cython数组。

这会生成一个cython.array,而不需要分配内存。默认情况下,cython.array使用stdlib.h mallocfree,因此您也应该使用默认的malloc,而不是任何特殊的CPython/Numpy分配器。

只有在为此free设置所有权的情况下才调用cython.array,默认情况下,只有在分配数据时才会调用cython.array。对于我们的情况,我们可以通过以下方式手动设置:

my_cyarr.free_data = True

因此,要返回一个一维数组,它将非常简单,如:

代码语言:javascript
运行
复制
from cython.view cimport array as cvarray

# ...
    cdef cvarray cvarr = <np.int32_t[:N]> data
    cvarr.free_data = True
    return np.asarray(cvarr)
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23872946

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档