为了加速自定义 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 崩溃阈值。
在
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