Python 的 C 扩展与 numpy 在 Python 代码的几次调用(5~10)后随机崩溃

问题描述 投票:0回答:1

为了加速自定义 MFCC 计算,我必须用 C 编写一些代码,用 Python 的 C API 和 numpy 的 C API 包装它,并使用 disutils.core 编译它,以便从 python 代码导入它。

我能够使用一个小样本成功编译并执行来自 python 的代码。

但是,一旦我使用 timeit 或 for 循环使用相同的输入连续调用相同的函数约 10 次,Python 就会停止,并且不会给出任何错误消息。

我不认为这是由于内存泄漏,因为输入数据是 150kB,而我当时有 GB 的备用 RAM。

最令人困惑的事情是什么?当函数被调用一次或两次时它就会起作用。当以两位数调用时,它似乎总是崩溃。

这是我的代码,转载:

#define PY_SSIZE_T_CLEAN
#include <stdio.h>
#include <stdlib.h>
#include "CMel.h"


typedef struct{
    int real, imag;
} COMPLEX;

static PyMethodDef CMelMethods[] = {
    {"mel_40", mel_40, METH_VARARGS},
    {NULL, NULL}
};

static PyModuleDef CMelModule = {
    PyModuleDef_HEAD_INIT, "CMel", "Python interface for C-code of integer-based Mel Spectrogram calculation", -1, CMelMethods
};

PyMODINIT_FUNC PyInit_CMel(void) {
    import_array();
    return PyModule_Create(&CMelModule);
}

static PyObject* mel_40(PyObject* self, PyObject* args) {
    /* Input: numpy array (nparrayin)
    *  Output: numpy array (nparrayout)
    *  Function: performs mel-spectrogram transformation of given pcm-style array
    *            Always assumes samplerate=16000, framelength=480, hoplength=240, coefficients=40
    */

    PyArrayObject* nparrayin, * nparrayout;
    int* carrayin, * carrayout;
    int numframes;

    // Check i/o types, throw error if something is wrong
    if (!PyArg_ParseTuple(args, "O!", &PyArray_Type, &nparrayin))  {
        PyErr_SetString(PyExc_ValueError, "[mel_40] Input array type mismatch!");
        return NULL;
        }
    
    if (nparrayin == NULL)  {
        PyErr_SetString(PyExc_ValueError, "[mel_40] Input array is NULL");
        return NULL;
        }
    if (!is_intvector(nparrayin)) {
        PyErr_SetString(PyExc_ValueError, "[is_intvector] Input array must be of type int16 or int32");
        return NULL;
        }

    // Copy numpy array to C array
    if (nparrayin->descr->type_num == NPY_INT16) {      // this is usually called
        carrayin = pyarray_int16_to_carrayptr_int32(nparrayin);
    }
    else { // if (nparrayin->descr->type_num == NPY_INT32) 
        carrayin = pyarray_int32_to_carrayptr_int32(nparrayin);
    }
    numframes = calculate_numframes(nparrayin->dimensions[0]);

    if (numframes <= 5) {
        PyErr_SetString(PyExc_ValueError, "[mel_40] Input data is too short");
        return NULL;
    }

    // Operate on arrays here
    carrayout = (int*)malloc((numframes - 5) * 40 * sizeof(int));
    Calculate_Melspectrogram(carrayin, carrayout, numframes - 5);

    // Create nparrayout for outputting to python
    const npy_intp dims[2] = {numframes-5, 40};
    nparrayout = (PyArrayObject*)PyArray_SimpleNewFromData(2, dims, NPY_INT32, carrayout);

    free(carrayin);
    // Ref: https://stackoverflow.com/questions/4657764/py-incref-decref-when
    Py_DECREF(nparrayin);
    return PyArray_Return(nparrayout);
}

int is_intvector(PyArrayObject *inputarray) {
    if (inputarray->descr->type_num != NPY_INT16 && inputarray->descr->type_num != NPY_INT32 || inputarray->nd != 1) return 0;
    return 1;
}

int* pyarray_int16_to_carrayptr_int32(PyArrayObject* inputarray) {
    // (int16) Numpy array -> (int32) C Array
    short* pyarray = (short*)inputarray->data;
    int* carray;
    int i, rows;

    rows = inputarray->dimensions[0];

    carray = (int*)malloc(sizeof(int) * rows);

    for (i = 0; i < rows; i++) {
        carray[i] = (int)pyarray[i];
    }
    return carray;
}

int* pyarray_int32_to_carrayptr_int32(PyArrayObject* inputarray) {
    // (int32) Numpy array -> (int32) C Array
    int* pyarray = (int*)inputarray->data;
    int* carray;
    int i, rows;

    rows = inputarray->dimensions[0];

    carray = (int*)malloc(sizeof(int) * rows);

    for (i = 0; i < rows; i++) {
        carray[i] = pyarray[i];
    }
    return carray;
}

int calculate_numframes(int numdata) {
    return ((numdata - 240) / 240);
}


void Calculate_Melspectrogram(int *inputarray, int *outputarray, int numframes) {
    COMPLEX *fftarray = (COMPLEX*)malloc(257 * sizeof(COMPLEX));
    int* window = (int*)calloc(514, sizeof(int));
    
    inputarray += 480;

    for (int i=0; i<numframes; i++) {
        memcpy(window, inputarray, 480 * sizeof(int));
        inputarray += 240;
        MFCC_Extract(window, fftarray);
        memcpy(outputarray, window, 40 * sizeof(int));
        outputarray += 40;
        
        //Reset fftarray
        memset(fftarray, 0, 257 * sizeof(COMPLEX));
        memset(window, 0, 514 * sizeof(int));
    }

    free(window);
    free(fftarray);
}

该函数的调用方式如下:

import numpy as np
import scipy.io.wavfile as wavfile
import timeit
from CMel import mel_40

samplerate, data = wavfile.read("./example.wav")

print(timeit.Timer(lambda: mel_40(data)).timeit(number=100))

C 代码使用以下代码编译:

python3 setup.py install

设置.py:

import os
import numpy as np
from sysconfig import get_paths
from distutils.core import setup, Extension

python_paths = get_paths()

CMelModule = Extension("CMel", define_macros=[('MAJOR_VERSION', '1'), ("MINOR_VERSION", "0")],
                       include_dirs=["/usr/local/include", os.path.join(np.get_include(), "numpy"), python_paths['include']],
                    #    libraries=["python36"],
                    #    library_dirs=[python_paths['stdlib']],
                       sources=["CMel.c", "melcalculations.c"])


def main():
    setup(name="CMel",
          version="1.0.0",
          description="Python interface for integer-based FFT and Mel Spectogram calculation",
          author="FB",
          author_email="[email protected]",
          ext_modules=[CMelModule])


if __name__ == "__main__":
    main()

注意,MFCC_Extract() 是 MFCC 提取发生的地方。在这里发布太长了,但我已经确保它工作正常,内部没有发生内存分配。

另外,奇怪的是,我尝试将 mel_40 函数的输出内容写入文件,这使其能够超过 ~10 崩溃阈值。

python c numpy python-c-api
1个回答
0
投票

Py_DECREF(nparrayin)
的末尾执行
mel_40()
是不正确的。参考
PyArg_ParseTuple()
Python 扩展文档 说:

请注意,提供给调用者的任何 Python 对象引用都是借用引用;不要减少它们的引用计数!

这里有一些代码,在每次调用

mel_40()
后打印输入数组的引用计数,以及带有和不带有 decref 的输出:

import sys
import numpy as np
from CMel import mel_40

data = np.arange(0, (5 + 5)*240 + 240, dtype=np.int16)
# add a couple extra references
data_1 = data_2 = data

print("initial refcount:", sys.getrefcount(data))
for i in range(5):
    result = mel_40(data)
    print("refcount:", sys.getrefcount(data))

Py_DECREF(nparrayin)

initial refcount: 4
refcount: 3
refcount: 2
double free or corruption (top)
[1]    753324 IOT instruction (core dumped)  python test.py

没有:

initial refcount: 4
refcount: 4
refcount: 4
refcount: 4
refcount: 4
refcount: 4
© www.soinside.com 2019 - 2024. All rights reserved.