Как освободить память, выделяемую внешними библиотеками C, взаимодействующими с модулем Cython, где память в конечном итоге возвращается процессу Python? - PullRequest
3 голосов
/ 14 мая 2019

Я новичок в 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;
}

Если у кого-нибудь есть какие-либо предложения о том, как и где я могу освободить эту память, это было бы очень признательно.

1 Ответ

1 голос
/ 14 мая 2019

Вы можете напрямую импортировать 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.
...