Как освободить память, выделенную в карте типов для массива структур argout?

Я использую SWIG, чтобы обернуть библиотеку C для Python. Один из методов C принимает буфер указателя на структуру и количество элементов для заполнения и заполняет указанные структуры. Для Python API я хочу предоставить только количество элементов и возвращаемое значение, которое будет кортежем заполненных структур.

  C     : int fill_widgets(widget_t *buffer, int num_widgets);
  Python: fill_widgets(num_widgets) -> (widget, widget,...)

Я написал карты типов, которые работают так, как я хочу - карты типов выделяют буфер на основе входного аргумента Python, передают буфер методу C, преобразуют его в кортеж Python, а затем возвращают кортеж виджетов. Но я не могу понять, если / когда / как мне нужно освободить память, выделенную в моей карте типов.

Первоначально я включил карту типов freearg, чтобы освободить буфер при выходе из функции-оболочки, но я считаю, что структуры, возвращаемые Python, все еще используют физическую память (т. Е. Память не копируется, я просто получаю прокси-указатель, который использует тот же буфер). Я также пытался установить флаг SWIG_POINTER_OWN при создании прокси-объектов (через SWIG_NewPointerObj), но поскольку я создаю прокси-указатель на каждый элемент в буфере, нет смысла освобождать их всех. В обоих случаях Python в конечном итоге завершает segfaulting при последующем вызове free().

Итак, не используя freearg в карте типов или SWIG_POINTER_OWN при создании прокси, как мне освободить память, когда кортеж структур Python выходит из области видимости?

Вот простой интерфейс SWIG, который демонстрирует, что у меня есть:

%module "test"

%typemap (in, numinputs=1) (BUF, NUM){
    $2 = PyInt_AsLong($input);
    $1 = ($1_type)calloc($2, sizeof($*1_type));
}

%typemap (argout) (BUF, NUM){
    PyObject *tpl = PyTuple_New($2);
    for ($2_ltype i=0; i<$2; i++)
    {
        PyTuple_SET_ITEM(tpl, i, SWIG_NewPointerObj(&$1[i], $1_descriptor, 0));
    }
    $result = SWIG_Python_AppendOutput($result, tpl);
}

%typemap (freearg) (BUF, NUM){
    //free($1);
}

%apply (BUF, NUM) {(widget_t *buf, int num_widgets)};

%inline {
typedef struct {int a; int b;} widget_t;

int fill_widgets(widget_t *buf, int num_widgets)
{
    for(int i=0; i<num_widgets; i++)
    {
        buf[i].a = i;
        buf[i].b = 2*i;
    }
    return num_widgets;
}
}

И пример для сборки / запуска:

$ swig -python test.i
$ gcc -I/path/to/python2.7 -shared -lpython2.7 test_wrap.c  -o _test.so
$ python
>>> import test
>>> _,widgets = test.fill_widgets(4)
>>> for w in widgets: print w.a, w.b
... 
0 0
1 2
2 4
3 6
>>> 

Пример использования fill_widgets из C:

int main()
{
    widget_t widgets[10];  // or widget_t *widgets = calloc(10, sizeof(widget_t))
    fill_widgets(widgets, 10);
}

1 ответ

Решение

Что делает это интересным, так это то, что у вас есть 1 буфер, но вы создали N прокси-объектов Python, каждый из которых находится в этом буфере.

Предполагая, что вы не хотите развлекать копирование объектов из этого буфера, вы получаете распределение 1:1 для сопоставления прокси-объектов Python, а затем избавляетесь от исходного буфера, к которому у нас есть одно решение. Цель здесь - убедиться, что каждый из объектов Python также содержит ссылку на объект, которому принадлежит память. При этом мы можем поддерживать высокий счетчик ссылок и освобождать память только тогда, когда уверены, что никто не может еще указывать на это.

Самое простое решение для этого - установить SWIG_POINTER_OWN для первого объекта в буфере (т. Е. Указатель, который действительно ссылается на память, из которой вы получаете обратно). calloc) и затем иметь любой другой прокси-объект, не владеющий памятью, но содержащий ссылку на тот, который имеет.

Чтобы реализовать это, мы делаем два изменения в вашей карте типов argout. Сначала мы устанавливаем SWIG_POINTER_OWN только для первого элемента кортежа. Во вторых звоним PyObject_SetAttrString для всех, кроме первого элемента, чтобы сохранить ссылку вокруг. Так что это выглядит так:

%typemap (argout) (BUF, NUM){
    PyObject *tpl = PyTuple_New($2);
    for ($2_ltype i=0; i<$2; i++)
    {
        PyObject *item = SWIG_NewPointerObj(&$1[i], $1_descriptor, 0==i?SWIG_POINTER_OWN:0);
        if (i) {
            PyObject_SetAttrString(item,"_buffer",PyTuple_GET_ITEM(tpl, 0));
        } 
        PyTuple_SET_ITEM(tpl, i, item);
    }
    $result = SWIG_Python_AppendOutput($result, tpl);
}

Мы можем проверить количество ссылок, как и ожидалось, в интерактивном режиме:

In [1]: import test

In [2]: import sys

In [3]: a,b=test.fill_widgets(20)

In [4]: sys.getrefcount(b[0])
Out[4]: 21

In [5]: sys.getrefcount(b[1])
Out[5]: 2

In [6]: b[1]._buffer
Out[6]: <test.widget_t; proxy of <Swig Object of type 'widget_t *' at 0xb2118d10> >

In [7]: b[1]._buffer == b[0]
Out[7]: True

In [8]: x,y,z = b[0:3]

In [9]: del a

In [10]: del b

In [11]: sys.getrefcount(x)
Out[11]: 4

In [12]: sys.getrefcount(y)
Out[12]: 2

In [13]: sys.getrefcount(z)
Out[13]: 2

In [14]: del x

In [15]: sys.getrefcount(y._buffer)
Out[15]: 3
Другие вопросы по тегам