2012-01-03 136 views
11

我有一个C函数mallocs()并填充浮点数的二维数组。它“返回”该地址和数组的大小。签名是我可以强制让一个numpy ndarray拥有它的内存吗?

int get_array_c(float** addr, int* nrows, int* ncols); 

我想从Python中调用它,所以我使用ctypes的。

import ctypes 
mylib = ctypes.cdll.LoadLibrary('mylib.so') 
get_array_c = mylib.get_array_c 

我从来没想过如何用ctypes指定参数类型。我倾向于为我正在使用的每个C函数编写一个python包装器,并确保在包装器中正确地获取类型。浮点数组是一个列 - 主要顺序的矩阵,我想把它作为numpy.ndarray。但它非常大,所以我想使用C函数分配的内存,而不是复制它。 (我刚刚发现这个PyBuffer_FromMemory东西,在这个StackOverflow的答案:https://stackoverflow.com/a/4355701/3691

buffer_from_memory = ctypes.pythonapi.PyBuffer_FromMemory 
buffer_from_memory.restype = ctypes.py_object 

import numpy 
def get_array_py(): 
    nrows = ctypes.c_int() 
    ncols = ctypes.c_int() 
    addr_ptr = ctypes.POINTER(ctypes.c_float)() 
    get_array_c(ctypes.byref(addr_ptr), ctypes.byref(nrows), ctypes.byref(ncols)) 
    buf = buffer_from_memory(addr_ptr, 4 * nrows * ncols) 
    return numpy.ndarray((nrows, ncols), dtype=numpy.float32, order='F', 
         buffer=buf) 

这似乎给我用正确的值的数组。但我很确定这是内存泄漏。

>>> a = get_array_py() 
>>> a.flags.owndata 
False 

该阵列不拥有内存。很公平;默认情况下,当从缓冲区创建数组时,它不应该。但在这种情况下,它应该。当numpy数组被删除时,我真的很喜欢python为我释放缓冲区内存。看起来如果我可以强制owndata为True,那应该这样做,但是owndata不可设置。

解决方案不能令人满意:

  1. 使get_array_py的调用者()负责释放内存。这太烦人了;调用者应该能够像对待任何其他numpy数组一样对待这个numpy数组。

  2. 在get_array_py中将原始数组复制到一个新的numpy数组中(使用它自己的单独内存),删除第一个数组,然后释放get_array_py()中的内存。返回副本而不是原始数组。这很烦人,因为它应该是不必要的内存拷贝。

有没有办法做我想做的事?我不能修改C函数本身,尽管我可以在库中添加另一个C函数,如果这有帮助的话。

+0

这听起来像一个痛苦的世界..我认为你是要[segfault hell](http://xkcd.com/371/) – wim 2012-01-03 07:27:17

+0

我试过这个以及没有成功使用ctypes。完整的扩展模块使这成为可能,但他们更多的工作来写。 – 2012-02-01 20:20:25

回答

1

我会倾向于具有从我的C库中导出两个功能:

int get_array_c_nomalloc(float* addr, int nrows, int ncols); /* Pass addr as argument */ 
int get_array_c(float **addr, int nrows, int ncols); /* Calls function above */ 

我会然后写我Python包装get_array_c的[1]〜分配阵列,然后调用get_array_c_nomalloc。然后Python 确实拥有内存。你可以将这个包装器集成到你的库中,这样你的用户永远不必知道get_array_c_nomalloc的存在。

[1]这不是一个真正的包装,而是一个适配器。

+0

对不起,我有get_array_c()错误的签名!它需要int _pointers_用于nrows和ncols - 我不知道数组有多大,所以我不能在python中预分配数组。 – 2012-01-03 08:44:09

+0

好吧,你也可以让你的python包装器使用一个对象来保存引用/访问内存,并使用终结器来释放数组......不知道这是否违反你的审美或不,但用户赢得'不得不明确地释放内存。 – Matthew 2012-01-03 15:32:03

6

我只是偶然发现了这个问题,这个问题在2013年8月仍然存在。Numpy对OWNDATA标志真的很挑剔:它无法在Python级别修改,所以ctypes很可能无法使用去做这个。在numpy的C-API级 - 现在我们正在谈论如何让Python扩展模块的完全不同的方式 - 一个有明确设置标志用:

PyArray_ENABLEFLAGS(arr, NPY_ARRAY_OWNDATA); 

在numpy的< 1.7,一个必须是连更明确:

((PyArrayObject*)arr)->flags |= NPY_OWNDATA; 

如果一个人在底层C功能/库的任何控制,最佳解决方案是将它传递从Python的适当大小的一个空numpy的阵列,以将结果存储在其基本原理是。内存分配应该始终在最高级别上进行,在这种情况下,应该在Python解释器的级别上进行。


由于kynan下面评论,如果你使用Cython,你必须手动曝光功能PyArray_ENABLEFLAGS,看到这个帖子Force NumPy ndarray to take ownership of its memory in Cython

相关文件为herehere

+0

我如何在Cython中实现同样的效果?不幸的是,'PyArray_ENABLEFLAGS'似乎不会暴露在'numpy.pxd'中。 – kynan 2014-05-07 13:18:41

+1

如果所需的功能未暴露给Cython,您可以修补Cython或编辑它手动生成的C文件。 – Stefan 2014-05-07 13:26:19

+0

这些对我来说都不是非常可持续的选择。我尝试在'pyx文件中扩展'numpy.pxd'暴露的东西[但没有运气](https://gist.github.com/kynan/ade36155b497c87e0bc5)。 – kynan 2014-05-07 16:44:16