Почему у моего Python C Extension утечка памяти? - PullRequest
5 голосов
/ 08 декабря 2008

Функция ниже берет дескриптор файла python, считывает упакованные двоичные данные из файла, создает словарь Python и возвращает его. Если я буду бесконечно его зацикливать, он будет постоянно потреблять оперативную память. Что не так с моим RefCounting?

static PyObject* __binParse_getDBHeader(PyObject *self, PyObject *args){

PyObject *o; //generic object
PyObject* pyDB = NULL; //this has to be a py file object

if (!PyArg_ParseTuple(args, "O", &pyDB)){
    return NULL;
} else {
    Py_INCREF(pyDB);
    if (!PyFile_Check(pyDB)){
        Py_DECREF(pyDB);
        PyErr_SetString(PyExc_IOError, "argument 1 must be open file handle");
        return NULL;
    }
}

FILE *fhDB = PyFile_AsFile(pyDB);

long offset = 0;
DB_HEADER *pdbHeader = malloc(sizeof(DB_HEADER));
fseek(fhDB,offset,SEEK_SET); //at the beginning
fread(pdbHeader, 1, sizeof(DB_HEADER), fhDB );
if (ferror(fhDB)){
    fclose(fhDB);
    Py_DECREF(pyDB);
    PyErr_SetString(PyExc_IOError, "failed reading database header");
    return NULL;
}
Py_DECREF(pyDB);

PyObject *pyDBHeader = PyDict_New();
Py_INCREF(pyDBHeader);

o=PyInt_FromLong(pdbHeader->version_number);
PyDict_SetItemString(pyDBHeader, "version", o);
Py_DECREF(o);

PyObject *pyTimeList = PyList_New(0);
Py_INCREF(pyTimeList);

int i;
for (i=0; i<NUM_DRAWERS; i++){
    //epochs
    o=PyInt_FromLong(pdbHeader->last_good_test[i]);
    PyList_Append(pyTimeList, o);
    Py_DECREF(o);
}
PyDict_SetItemString(pyDBHeader, "lastTest", pyTimeList);
Py_DECREF(pyTimeList);

o=PyInt_FromLong(pdbHeader->temp);
PyDict_SetItemString(pyDBHeader, "temp", o);
Py_DECREF(o);

free(pdbHeader);
return (pyDBHeader);
}

Спасибо, что заглянули,

LarsenMTL

Ответы [ 3 ]

16 голосов
/ 08 декабря 2008

PyDict_New() возвращает новую ссылку, проверьте документы для PyDict. Таким образом, если вы увеличите рефконт сразу после его создания, у вас есть две ссылки на него. Один передается вызывающей стороне, когда вы возвращаете его в качестве значения результата, но другой никогда не уходит.

Вам также не нужно увеличивать pyTimeList. Это твое, когда ты его создаешь. Тем не менее, вам нужно расшифровать его, но вы можете дешифровать его только один раз, так что он тоже просочился.

Вам также не нужно звонить Py_INCREF на pyDB. Это заимствованная ссылка, и она не исчезнет, ​​пока ваша функция не вернется, потому что на нее все еще ссылаются в кадре с меньшим стеком.

Только если вы хотите где-то сохранить ссылку в другой структуре, вам нужно увеличить количество ссылок.

Cf. API документы

5 голосов
/ 08 декабря 2008

OT: использование последовательных вызовов на PyList_Append является проблемой производительности. Поскольку вы знаете, сколько результатов вы получите заранее, вы можете использовать:

PyObject *pyTimeList = PyList_New(NUM_DRAWERS);
int i;
for (i=0; i<NUM_DRAWERS; i++){
    o = PyInt_FromLong(pdbHeader->last_good_test[i]);
    PyList_SET_ITEM(pyTimeList, i, o);
}

Имейте в виду, что вы не можете уменьшить refcount на o после вызова PyList_SET_ITEM, потому что он "крадет" ссылку. Проверьте документы .

2 голосов
/ 08 декабря 2008

Я не знаю о Python-C. Тем не менее, мой опыт подсчета ссылок COM говорит, что недавно созданный объект с подсчетом ссылок имеет счетчик ссылок 1 . Таким образом, ваш Py_INCREF (pyDB) после PyArg_ParseTuple (args, "O", & pyDB) и PyObject * pyDBHeader = PyDict_New (); виновник Их количество ссылок уже 2.

...