kyn*_*nan 13 python arrays numpy cython
在接下来的回答"我可以强迫一个numpy ndarray获取其记忆的所有权吗?" 我尝试PyArray_ENABLEFLAGS通过Cython的NumPy包装器使用Python C API函数,发现它没有暴露.
以下尝试手动公开它(这只是重现故障的最小示例)
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)
Run Code Online (Sandbox Code Playgroud)
失败并出现编译错误:
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 *'
Run Code Online (Sandbox Code Playgroud)
我的问题:在这种情况下,这是正确的方法吗?如果是这样,我做错了什么?如果没有,我如何强制NumPy在Cython中获得所有权,而不需要转到C扩展模块?
Ste*_*fan 17
您只是在接口定义中有一些小错误.以下对我有用:
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
Run Code Online (Sandbox Code Playgroud)
这是我的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)
Run Code Online (Sandbox Code Playgroud)
用python setup.py build_ext --inplace.构建.然后验证数据是否实际拥有:
import _owndata
arr = _owndata.test()
print arr.flags
Run Code Online (Sandbox Code Playgroud)
其中,你应该看到OWNDATA : True.
而且是,这绝对是处理这个正确的方式,因为numpy.pxd究竟到所有其他功能导出到用Cython同样的事情.
@Stefan的解决方案适用于大多数情况,但有点脆弱。NumpyPyDataMem_NEW/PyDataMem_FREE用于内存管理,它是一个实现细节,这些调用被映射到通常的malloc/free+ 一些内存跟踪(我不知道 Stefan 的解决方案对内存跟踪有什么影响,至少它似乎不会崩溃)。
还有更多深奥的情况可能,其中freenumpy-library 不使用与malloccython 代码相同的内存分配器(链接到不同的运行时,例如在此github-issue或此SO-post 中)。
传递/管理数据所有权的正确工具是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
...
Run Code Online (Sandbox Code Playgroud)
现在,我们使用MemoryNanny-object 作为内存的哨兵,一旦 parent-numpy-array 被破坏,它就会被释放。代码有点笨拙,因为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
...
Run Code Online (Sandbox Code Playgroud)
这是一个示例,说明如何调用此功能:
%%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)
Run Code Online (Sandbox Code Playgroud)
可以按如下方式使用:
>>> m = create()
nanny for ptr= 94339864945184
>>> m.flags
...
OWNDATA : False
...
>>> m[0]
42.0
>>> del m
freeing ptr= 94339864945184
Run Code Online (Sandbox Code Playgroud)
与预期的结果/输出。
注意:结果数组并不真正拥有数据(即标志 return 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)
Run Code Online (Sandbox Code Playgroud)
以下测试表明,保姆工作:
nanny=create_vector(10**8) # top shows additional 800MB memory are used
del nanny # top shows, this additional memory is no longer used.
Run Code Online (Sandbox Code Playgroud)
最新的 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
这本身并不能传递所有权。
值得注意的是,这个调用生成了一个 Cython 数组,通过array_cwrapper.
这会生成cython.array, 而不分配内存。的cython.array用途stdlib.h malloc及free在默认情况下,所以可以预计,使用默认的malloc,为好,而不是任何特殊的CPython / NumPy的分配器。
free仅在为此设置所有权时才调用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)
Run Code Online (Sandbox Code Playgroud)