首页 > 解决方案 > 如何释放与 Cython 模块接口的外部 C 库分配的内存,其中内存最终返回给 Python 进程?

问题描述

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

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

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

示例 Python 代码

from examplecython import *

def foo(data):
    context = data.context
    value = call_pyx_function(context, data)
    return value

def bar(results):
    for data in results:
        res = foo(data)
        do_something_with_res(res)
        # I want to free here

示例 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 代码


#define BUFSIZE 256

char *
my_function(const char * context, int x) {
    char * retbuf;
    int res;

    retbuf = (char *)malloc(BUFSIZE * sizeof(char));

    res = do_some_math(x, context);

    int length = snprintf(retbuf, BUFSIZE, "%d", res);
    if (length >= BUFSIZE) {
        exit(EXIT_FAILURE);
    }

    return retbuf;
}

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

标签: pythoncmemory-leakscython

解决方案


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

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因为即使某些东西引发异常,您也希望它被释放)

__del__您可以使用上下文管理器或在/中删除的包装器使这更容易__dealloc__

@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)

或者(可能会在很久以后被释放,可能会更慢,但(几乎)保证最终会被释放)

# (in pyx file)
cdef class MallocedResource:
    cdef void* res;

    def __init__(self, res):
        # Note: This "steals" res. Don't free `res`
        # as it is freed when this class's storage is freed
        self.res = <void *>res

    def __dealloc__(self):
        free(self.res)

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

# No need to change python code, so you can't forget to use try/finally.

推荐阅读