Ошибка Ctypes mallo c при освобождении экземпляров классов - PullRequest
0 голосов
/ 29 февраля 2020

Я пытаюсь связать некоторые c служебные функции с моим python через ctypes. На уровне python после создания экземпляра класса он связывает внешнюю библиотеку и сохраняет в ней некоторые данные. При уничтожении класса все выделенные переменные во внешней библиотеке освобождаются.

Минимальный рабочий пример, иллюстрирующий проблему, см. Ниже.

1) Python код

# problematic.py
import ctypes as ct
import numpy as np
from numpy.ctypeslib import ndpointer
import gc

class Problem(object):

    def __init__(self, len_arr):

        # Init the array to be passed
        self.len_arr = len_arr
        myarr = np.random.rand(len_arr)

        # Link library and its functions
        path_to_ext = /path/to/ext
        self.ext = ct.CDLL(path_to_ext + "libproblematic.so")
        self.ext.load_myarr.restype = None
        self.ext.load_myarr.argtypes = [ndpointer(ct.c_double, flags="C_CONTIGUOUS"), ct.c_long]
        self.ext.mult_myarr.restype = ct.c_void_p
        self.ext.mult_myarr.argtypes = [np.ctypeslib.ndpointer(dtype=np.float64), np.ctypeslib.ndpointer(dtype=np.float64)]
        self.ext.free_arr.argtype = None
        self.ext.free_arr.restype = None

        # Pass the random array to C
        self.ext.load_myarr(myarr.astype(np.float64), self.len_arr)

    def __del__(self):
        # Free the memory in C
        self.ext.free_arr()
        print("Freed instance")


    def multiply_arr(self, to_multiply):
        assert(len(to_multiply) == self.len_arr)
        result = np.zeros(self.len_arr)
        self.ext.mult_myarr(to_multiply.astype(np.float64), result)
        return result


# Run some various usecases
if __name__ == "__main__":

    try_1 = False
    try_2 = False
    try_3 = False
    try_4 = False

    # This works
    if try_1:
        for i in range(10):
            print(i)
            problem1 = Problem(10)
            result1 = problem1.multiply_arr(np.ones(10))
            result2 = problem1.multiply_arr(np.ones(10))
            assert(np.array_equal(result1, result2))
            del problem1
            gc.collect()

    # This works
    if try_2:
        for i in range(10):
            print(i)
            problem1 = Problem(10)
            result1 = problem1.multiply_arr(np.ones(10))
            del problem1
            gc.collect()
            problem2 = Problem(15)
            result2 = problem2.multiply_arr(np.ones(15))
            del problem2
            gc.collect()
            problem1 = Problem(10)
            result1 = problem1.multiply_arr(np.ones(10))
            del problem1
            gc.collect()

    # This works once, but when called again, breaks at the 
    # final iteration
    if try_3:
        for i in range(10):
            print(i)
            problem1 = Problem(10)
            result1 = problem1.multiply_arr(np.ones(10))
            problem2 = Problem(15)
            result2 = problem2.multiply_arr(np.ones(15))

    # This breaks in first loop at del problem2
    # --> pointer being freed was not allocated
    if try_4:
        for i in range(10):
            print(i)
            problem1 = Problem(10)
            result1 = problem1.multiply_arr(np.ones(10))
            gc.collect()
            problem2 = Problem(15)
            result2 = problem2.multiply_arr(np.ones(15))
            del problem1
            print("Deleted problem1")
            gc.collect()
            print("Collected problem1")
            del problem2
            print("Deleted problem2")
            gc.collect()
            print("Collected problem2")

2) Внешний C код

// libproblematic.c
// Compile with gcc-7 -fPIC -shared -o libproblematic.so libproblematic.c -std=c99"

#include <stdio.h>
#include <stdlib.h>

int len_arr;
double *myarr = NULL;

// Allocate global variable according to input from python
void load_myarr(double *arr_in, int len_arr);

// Multiply two vectors elementwise. This should just illustrate
// potential usecases
void mult_myarr(double *new_arr, double *res);

// Frees the global variable
void free_arr();


void load_myarr(double *arr_in, int len_arr_in){
    len_arr = len_arr_in;
    myarr = malloc(sizeof(double)*len_arr);
    for (int j = 0; j<len_arr; j++){
            myarr[j] = arr_in[j];
        }
}

void mult_myarr(double *new_arr, double *res){
    for (int j = 0; j<len_arr; j++){
        res[j] = new_arr[j]*myarr[j];
    }
}

void free_arr(){
    free(myarr);
}

По сути, код выполняется безопасно только при уничтожении экземпляра класса перед созданием нового, что полностью подрывает любое практическое использование. Одним из возможных решений (я полагаю) было бы избавление от глобальных переменных на уровне C и передача их всех в качестве параметров в функции; в принципе это было бы возможно, но для моего реального кода интерфейс класса стал бы действительно уродливым (существует около 30 глобальных переменных).

Известны ли вам какие-либо другие (более приятные) способы избавления от ошибок памяти ?

Фактический сценарий использования имеет распараллеливание openmp на уровне C, поэтому в идеале решение не должно вызывать никаких проблем.

...