cython的类型转换导致的内存泄漏问题

最近发现一个cython写的服务在持续请求下会有内存持续上涨现象,一波压测之后,发现确实有内存泄漏问题。于是先试了下valgrind,发现没有definite lost的内存,那必定是python里的对象还有引用,但没有释放,这下就麻烦了,只能靠眼力来分析代码了。

花了一天工夫最终定位是这句:

return <object>reply

改成如下即ok:

replyObj = <object>reply
Py_DECREF(replyObj)
return replyObj

这个时候就来深挖一下cython的类型转换问题。

为什么会导致内存泄漏?

特别注意cython里关于类型转换的一段描述:

Casting with cast(object, ...) creates an owned reference. Cython will automatically perform a Py_INCREF and Py_DECREF operation. Casting to cast(pointer(PyObject), ...) creates a borrowed reference, leaving the refcount unchanged.

首先注意,类型转换可以使用<type>variable写法,也可以使用cast(type, variable);

然后注意,可以将python类型转成c类型,也可以将c类型转成python类型,可以将c语言指针转成python对象,也可以将python对象转成c语言指针;

最重要的是,按这里描述的,把一个c语言指针转成python的object,那么会创建一个owned引用,就是引用计数加1(cython在转换成c代码时会给加上)。注意,cython的类型转换是不会检查类型的,可以是void *,不过通常应该是PyObject *。而如果反之,将一个python对象转成c指针,却是创建了一个borrowed引用,该对象的引用计数不会变。

我们通常写一个c module扩展时,返回的PyObject *obj,习惯上会保证引用计数为1,而到pyx里,再转成具体的python对象,引用计数就会变成2,如果不减一下,就会导致内存泄漏问题。正如上面的代码所示。

如果将pxd里使用PyObject*写成object会怎么样呢?

上面的问题还涉及到pxd里是怎么定义的,因为cython实际只对pyx文件进行转换,比如定义是:

object uint32_encode(uint32_t* seeds, uint32_t seeds_cnt, uint32_t n)

或者是:

PyObject* uint32_encode(uint32_t* seeds, uint32_t seeds_cnt, uint32_t n)

此两种声明是一样的,第二种声明跟c模板里的函数原型完全一样。前者不会导致内存泄漏,而后者会导致内存泄漏。

其实,就相当于一个语法糖,见到<type>variable的表达,就会按照既定方法转换,引用计数就会加1,没遇到此表达式,就不会加1。所以pxd里的声明方式不一样,是否需要Py_DECREF也不一样。

发表于 2023年01月08日 23:50   评论:0   阅读:1182  



回到顶部

首页 | 关于我 | 关于本站 | 站内留言 | rss
python logo   django logo   tornado logo