Я пытаюсь связать некоторые 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, поэтому в идеале решение не должно вызывать никаких проблем.