【问题标题】:Can I force a numpy ndarray to take ownership of its memory?我可以强制一个 numpy ndarray 拥有它的内存吗?
【发布时间】:2012-01-03 06:36:06
【问题描述】:

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

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 函数。

【问题讨论】:

  • 这听起来像是一个痛苦的世界。我想你是在问segfault hell
  • 我也试过这个,但没有成功使用 ctypes。一个完整的扩展模块使这成为可能,但它们需要编写更多的工作。

标签: python c numpy free ctypes


【解决方案1】:

我只是偶然发现了这个问题,这在 2013 年 8 月仍然是一个问题。Numpy 对 OWNDATA 标志真的很挑剔:在 Python 级别上无法修改它,因此 ctypes 很可能不会能够做到这一点。在 numpy C-API 级别上——现在我们正在讨论一种完全不同的制作 Python 扩展模块的方法——必须明确设置标志:

PyArray_ENABLEFLAGS(arr, NPY_ARRAY_OWNDATA);

在 numpy

((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

【讨论】:

  • 如何在 Cython 中实现相同的目标?不幸的是PyArray_ENABLEFLAGS似乎没有暴露在numpy.pxd中。
  • 如果所需的功能没有暴露给 Cython,您可以修补 Cython 或编辑它手动生成的 C 文件。
  • 对我来说,这两种选择都不是非常可持续的选择。我尝试在我的 pyx 文件 but had no luck with that 中扩展 numpy.pxd 公开的内容。
  • @user443854:看来PyArray_UpdateFlags() 不允许您更新所有标志,尤其是NPY_OWNDATA (如果您尝试它会被忽略;请参阅numpy 源中的flagsobject.c)。新的PyArray_ENABLEFLAGS() 只是上述位设置表达式的内联函数。
【解决方案2】:

我倾向于从我的 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] 这不再是真正的包装器,而是一个适配器。

【讨论】:

  • 抱歉,get_array_c() 的签名错误!它接受 nrows 和 ncols 的 int pointers ——我不知道数组会有多大,所以我不能在 python 中预先分配数组。
  • 好吧,你也可以让你的python包装器使用一个对象来保存引用/访问内存,并使用终结器来释放数组......不知道这是否违反了你的审美或不是,但用户不必显式释放内存。
猜你喜欢
  • 2014-07-15
  • 2020-11-15
  • 2012-07-18
  • 2012-08-11
  • 2011-12-09
  • 1970-01-01
  • 2020-09-23
  • 1970-01-01
相关资源
最近更新 更多