我可以强制 numpy ndarray 获取其内存的所有权吗?

问题描述 投票:0回答:2

我有一个 C 函数,可以使用 malloc() 并填充二维浮点数组。它“返回”该地址和数组的大小。签名是

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函数分配的内存,而不是复制它。 (我刚刚在 StackOverflow 答案中找到了 PyBuffer_FromMemory 的东西: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 函数。

python c numpy free ctypes
2个回答
6
投票

我只是偶然发现了这个问题,这在 2013 年 8 月仍然是一个问题。Numpy 对

OWNDATA
标志非常挑剔:无法在 Python 级别上对其进行修改,因此 ctypes 很可能无法做这个。在 numpy C-API 级别 - 现在我们正在讨论一种完全不同的制作 Python 扩展模块的方法 - 必须使用以下方式显式设置标志:

PyArray_ENABLEFLAGS(arr, NPY_ARRAY_OWNDATA);

在 numpy 上 < 1.7, one had to be even more explicit:

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

如果对底层 C 函数/库有任何控制权,最好的解决方案是从 Python 传递一个适当大小的空 numpy 数组来存储结果。基本原则是内存分配应始终在可能的最高级别,在本例中是 Python 解释器的级别。


正如 kynan 在下面评论的那样,如果您使用

Cython
,则必须手动公开函数
PyArray_ENABLEFLAGS
,请参阅这篇文章强制 NumPy ndarray 在 Cython 中取得其内存的所有权

相关文档在这里这里


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 */

然后我会编写 get_array_c 的 Python 包装器 [1] 来分配数组,然后调用 get_array_c_nomalloc。那么Python确实拥有内存。您可以将此包装器集成到您的库中,这样您的用户就不必知道 get_array_c_nomalloc 的存在。

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

© www.soinside.com 2019 - 2024. All rights reserved.