【问题标题】:Cython : return a <object>PyObject*, will it leak?Cython:返回一个 <object>PyObject*,它会泄漏吗?
【发布时间】:2015-09-14 04:33:23
【问题描述】:

我目前正在使用 Cython 包装一个库。为此,我想重用纯 C 绑定的一个函数。

这是基本设置:

  • mylib.pxd
  • old_lib.c
  • old_lib.h

在 mylib.pxd 我做:

cdef extern from old_lib.h:
    PyObject* get_pyobject()

然后将old_lib.c 作为我的扩展中的源文件传递:

setup(ext_modules=[Extension("mylib", sources=["mylib.pxd", "old_lib.c"])])

在 mylib.pxd 中,我使用 get_pyobject 创建一个我想返回的新对象,如下所示:

cdef PyObject* ptr
ptr = get_pyobject()
return <object>ptr

这给了我想要的行为,但我担心这会泄露 ptr 引用。

会吗?我很困惑,因为我发现(旧)引用说你应该自己管理 PyObject* 引用并相应地调用 Py_INCREF/DECREF 但似乎在 Cython FAQ 他们说:

请注意,对象的生命周期仅绑定到其拥有的引用,而不绑定到碰巧指向它的任何 C 指针。

这是否意味着每当返回值被丢弃时,ptr 就会被垃圾回收?

old_lib.c 中的流程是这样的:

PyObject* get_pyobject()
{
     PyTypedObject* typeptr = PyObject_NEW_VAR(MyType, &Type, size)
     fill_attribute(typeptr->attrib)
     return (PyObject*)typeptr
}

其中PyObject_NEW_VAR 在python 标准库(我的版本中为objimpl.h:196)中使用PyObject_InitVar 实现。因此,返回的引用是一个借来的引用,但由于使用了PyObject_MALLOC,我猜这是对该对象的唯一引用。相关代码:

#define PyObject_NEW_VAR(type, typeobj, n) \
( (type *) PyObject_InitVar( \
       (PyVarObject *) PyObject_MALLOC(_PyObject_VAR_SIZE((typeobj),(n)) ),\
       (typeobj), (n)) )

编辑: 我已经检查过了,当使用上面的代码时,sys.getrefcount 返回 3。据我所知,当我创建对象时,它的引用计数为 1。然后,当将其转换为 object 时,它的引用计数是撞到 2。因此它永远不会被垃圾收集(除非有一种方法可以删除一个只有一个可访问指针的对象的两个引用计数)和泄漏。 如果我插入一个 PY_DECREF,它仍然可以正常工作并正确返回 2。我还花时间直接在 Cython 中重写了该函数,它返回 2。

【问题讨论】:

  • 您可以在 Cython 中查找并打印 PyObjectob_refcnt 字段。在得出任何明确结论之前,我会仔细检查它是否设置为您认为应该的(我认为您对没有内存泄漏的代码是错误的,但我不确定 - 看看tiran.bitbucket.org/python-lcov/Objects/object.c.gcov.htmlPyObject_InitVar,特别是它所调用的_Py_NewReference))
  • 感谢您的洞察力。事实上,我的物品正在泄漏。如果有人能提出一个很好的解释,我很乐意接受!

标签: python c memory-leaks cython


【解决方案1】:

查看old documentation PyObject_NEW_VAR 是函数PyObject_NewVar 的宏版本,它(正如@MadPhysicist 所说)返回一个“新引用”(即引用计数为1)。我怀疑不再鼓励您使用该宏,因此它从最近的文档中消失了。

事实上,它是根据返回“借用引用”的东西来实现的,这可能应该被视为实现细节,而不是意味着它本身返回“借用引用”。

关于 Cython 的行为,转换为 &lt;object&gt; 会增加引用计数,因此会导致内存泄漏。我建议的诊断方法是查看引用计数,如下所示:

from cpython.ref cimport PyObject # somewhere at the top

def whatever_function():
    cdef PyObject* ptr
    ptr = get_pyobject()
    print ptr.ob_refcnt # prints 1
    ret_val = <object>ptr
    print ptr.ob_refcnt # prints 2,
        # but it will only every be decremented to 1, so never be freed
    return ret_val

在修复它方面,您有两个选择 - 您可以自己减少一次引用计数,或者您可以更改函数的 Cython 包装

cdef extern from old_lib.h:
   object get_pyobject()

(不要担心它与头文件不完全匹配!)。 Cython 将此解释为“get_pyobject() 返回一个新引用,因此我们不会自己增加它,而是从这里自动处理引用计数。”

【讨论】:

  • 谢谢,这正是我要找的!
猜你喜欢
  • 1970-01-01
  • 2011-06-16
  • 1970-01-01
  • 2015-06-18
  • 2021-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-05
相关资源
最近更新 更多