在这个答案是:“我能强迫一只矮胖的精灵拥有它的记忆吗?”之后,我尝试通过Cython的NumPy包装器使用Python函数PyArray_ENABLEFLAGS
,发现它没有公开。
下面是手动公开它的尝试(这只是一个再现失败的最小示例)
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)
编译错误失败:
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扩展模块呢?
发布于 2014-05-26 15:40:08
您只是在接口定义中有一些小错误。以下几点对我有用:
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
文件:
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
构建。然后验证数据是否实际拥有:
import _owndata
arr = _owndata.test()
print arr.flags
在其他方面,您应该看到OWNDATA : True
。
是的,这绝对是处理这个问题的正确方法,因为numpy.pxd
在将所有其他函数导出到Cython时都做了完全相同的事情。
发布于 2019-05-02 20:35:30
@Stefan解适用于大多数场景,但有些脆弱。Numpy使用PyDataMem_NEW/PyDataMem_FREE
进行内存管理,这是一个实现细节,这些调用映射到通常的malloc/free
+某些内存跟踪(我不知道Stefan的解决方案对内存跟踪有什么影响,至少它似乎不会崩溃)。
还有更复杂的情况,如numpy库中的free
没有在cython代码中使用与malloc
相同的内存分配器(针对不同的运行时链接,例如在github-发行或所以-邮寄中)。
传递/管理数据所有权的正确工具是PyArray_SetBaseObject.
首先,我们需要一个python对象,它负责释放内存。我在这里使用的是一个自制的cdef类(主要是因为日志/演示),但显然还有其他可能性:
%%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不会自动处理该引用:
%%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
...
下面是一个例子,如何调用此功能:
%%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)
可用于以下方面:
>>> 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
%%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)
以下测试显示,保姆工作:
nanny=create_vector(10**8) # top shows additional 800MB memory are used
del nanny # top shows, this additional memory is no longer used.
发布于 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
malloc
和free
,因此您也应该使用默认的malloc,而不是任何特殊的CPython/Numpy分配器。
只有在为此free
设置所有权的情况下才调用cython.array
,默认情况下,只有在分配数据时才会调用cython.array
。对于我们的情况,我们可以通过以下方式手动设置:
my_cyarr.free_data = True
因此,要返回一个一维数组,它将非常简单,如:
from cython.view cimport array as cvarray
# ...
cdef cvarray cvarr = <np.int32_t[:N]> data
cvarr.free_data = True
return np.asarray(cvarr)
https://stackoverflow.com/questions/23872946
复制相似问题