如何释放与Cython模块接口的外部C库分配的内存,在Cython模块中内存最终返回到Python进程?

如何释放与Cython模块接口的外部C库分配的内存,在Cython模块中内存最终返回到Python进程?,python,c,memory-leaks,cython,Python,C,Memory Leaks,Cython,我是Cython的新手,但基本上我有一个需要显著提高性能的应用程序,所以我的团队和我正试图重写Cython和C中的瓶颈 对于我们应用程序中最慢的部分,我编写了一些C代码,这些代码被编译到一个库中,cdef extern导入到Cython模块中,我相信这是一个.pyx文件。本质上,pyx文件中的代码基本上只是一个包装器,用于返回对C库函数的调用。最后,还有一个Python进程(主应用程序),它导入pyx文件中定义的所有函数并使用这些结果 我相信我有内存泄漏,因为在C代码中,我需要传递给Python

我是Cython的新手,但基本上我有一个需要显著提高性能的应用程序,所以我的团队和我正试图重写Cython和C中的瓶颈

对于我们应用程序中最慢的部分,我编写了一些C代码,这些代码被编译到一个库中,
cdef extern
导入到Cython模块中,我相信这是一个
.pyx
文件。本质上,
pyx
文件中的代码基本上只是一个包装器,用于返回对C库函数的调用。最后,还有一个Python进程(主应用程序),它导入
pyx
文件中定义的所有函数并使用这些结果

我相信我有内存泄漏,因为在C代码中,我需要传递给Python进程的结果有时是动态分配的。我的问题是,一旦Python进程使用了内存,我不知道如何释放它

Python代码示例

cdef extern from "my_lib.h"
    char * my_function(const char * context, int data)

def call_pyx_function(context: bytes, int x):
    return my_function(context, x)
来自示例cython导入*
def foo(数据):
context=data.context
值=调用函数(上下文、数据)
返回值
def bar(结果):
对于结果中的数据:
res=foo(数据)
用res做某事(res)
#我想在这里自由
Cython代码示例

cdef extern from "my_lib.h"
    char * my_function(const char * context, int data)

def call_pyx_function(context: bytes, int x):
    return my_function(context, x)
示例C代码

cdef extern from "my_lib.h"
    char * my_function(const char * context, int data)

def call_pyx_function(context: bytes, int x):
    return my_function(context, x)

#定义bufsize256
煤焦*
my_函数(常量字符*上下文,int x){
char*retbuf;
国际关系;
retbuf=(char*)malloc(BUFSIZE*sizeof(char));
res=做一些数学(x,上下文);
int length=snprintf(retbuf,BUFSIZE,“%d”,res);
如果(长度>=BUFSIZE){
退出(退出失败);
}
返回retbuf;
}

如果有人对我如何以及在何处释放此内存有任何建议,我们将不胜感激。

您可以直接从
libc.stdlib
导入
free

from libc.stdlib cimport free

def bar(results):
    for data in results:
        res = foo(data)
        try:
            do_something_with_res(res)
        finally:
            free(res)
(注意,您需要
try/finally
,因为您希望即使有东西抛出异常,也能释放它)

您可以使用上下文管理器或在
\uuuu del\uuuuu
/
\uuuu dealloc\uuuu
中删除的包装器来简化此过程:

@contextlib.contextmanager
def freeing(res):
    try:
        yield res
    finally:
        free(res)

def bar(results):
    for data in results:
        with freeing(foo(data)) as res:
            do_something_with_res(res)
或者(可能会晚很多,可能会慢一些,但(几乎)最终肯定会被释放)

#(在pyx文件中)
cdef类MallocedResource:
cdef void*res;
定义初始值(self,res):
#注意:此“窃取”资源。不释放资源`
#当释放此类的存储时,它将被释放
self.res=res
def uu dealoc uu(自我):
免费(self.res)
def call_pyx_函数(上下文:字节,int x):
返回mallocedresource(my_函数(上下文,x))
#无需更改python代码,因此您不会忘记使用try/finally。

您编写了一个返回已分配内存的函数。还要编写一个释放内存的函数。将所述函数也导出到Python。现在,您可以使用终结器将所有内容封装在Python类中。我非常感谢您为这个答案所做的努力。我相信这会对我的情况有效,但我喜欢将Cython代码与Python代码分开的想法,因此我将尝试@n.m.提供的建议-如果这不起作用,我将尝试一下。我犹豫不决的一部分原因是对装饰师没有很强的把握。您愿意再解释一下
@contextlib.contextmanager
的意图吗?@pickle这与@n.m.的答案基本相同,只是它直接从C stdlib导入
free
,而不是在C代码中创建包装并导入(您可以在pyx文件中导入
cimport
)。至于
@contextlib.contextmanager
,它基本上将函数转换成可以在
中使用的东西(在
with
块结束后,调用
free
,比
try/finally:free
干净得多)。只是想更正答案:@ArdaAytekin Good catch。在这种情况下,只需将方法重命名为
\uuuu dealoc\uuuu
,就可以解决这个问题。Thanks@Artyer是的,的确如此。还请注意,
\uu cinit\uu()
是C相关内存管理的初始值设定项(文档中提供了有关细节的更多信息)。我只是在搜索另一个问题时偶然发现了这篇文章,并注意到了你的反应。否则,我同意这是一个很好的答案,它也涉及到上下文管理器。顺便说一句,解释中还有一个小错误:
\uu del\uu
(不是代码摘录)。