You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python C扩展中正确内存释放问题求助

内存泄漏修复:C扩展中NumPy数组的内存管理问题

问题背景

你实现了生成延迟向量的C函数delay_vectors,并编写Python包装器将其暴露给NumPy。循环调用该函数时内存持续增长,即使尝试设置NPY_ARRAY_OWNDATA标记也无法解决内存泄漏问题。

原C函数实现

double** delay_vectors(const double* ts, int L, int tau, int p) {
    int i, j;

    // Allocate contiguous memory for v_i_list
    double **v_i_list = (double **)malloc((L-tau*(p-1)) * sizeof(double *));
    v_i_list[0] = (double *)malloc((L-tau*(p-1)) * p * sizeof(double));
    
    for (int i = 1; i < (L-tau*(p-1)); i++) {
        v_i_list[i] = v_i_list[i - 1] + p;
    }
    

    for (i = 0; i < L - tau * (p - 1); ++i) {
        for (j = 0; j < p; ++j) {
            v_i_list[i][j] = ts[i + j * tau];
        }
    }

    return v_i_list;
}

原Python包装器实现

static PyObject* delay_vectors_wrapper(PyObject* self, PyObject* args) {
    PyObject *input_array;
    PyArrayObject *input_array_np;
    int tau, p;

    // Parse the input arguments
    if (!PyArg_ParseTuple(args, "Oii", &input_array, &tau, &p)) {
        return NULL;
    }

    // Convert the input object to a NumPy array
    input_array_np = (PyArrayObject *)PyArray_FROM_OTF(input_array, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);

    if (input_array_np == NULL) {
        return NULL;
    }


    int ndim = PyArray_NDIM(input_array_np);
    if (ndim != 1) {
        PyErr_SetString(PyExc_ValueError, "Input array must be 1-dimensional.");
        Py_DECREF(input_array_np);
        return NULL;
    }
    
    int L = PyArray_DIM(input_array_np, 0);

    // Get data pointer from the NumPy array
    double *ts = (double *)PyArray_DATA(input_array_np);

    double** result = delay_vectors(ts, L, tau, p);

    // Convert the C result to a np array
    npy_intp dims[2] = {L - tau * (p - 1), p};
    PyObject* output_array = PyArray_SimpleNewFromData(2, dims, NPY_DOUBLE, result);
    

    
    Py_DECREF(input_array_np);

    return output_array;
}

泄漏原因分析

你的delay_vectors函数分配了两块独立内存:

  1. 指针数组v_i_list(存储每行的指针)
  2. 连续的数据块v_i_list[0](存储实际延迟向量数据)

而PyArray_SimpleNewFromData仅对传入的data指针(你这里错误传入了result,实际应为result[0])进行包装。即使设置NPY_ARRAY_OWNDATA,NumPy也只会释放result[0]指向的数据块,指针数组v_i_list的内存永远不会被回收,导致每次调用都泄漏这块内存。

修复方案

方案1:简化内存分配(推荐)

既然数据已经是连续存储的,完全不需要二级指针数组。修改delay_vectors直接返回单一的连续数据块,让NumPy直接管理这块内存:

修改后的C函数

double* delay_vectors(const double* ts, int L, int tau, int p) {
    int i, j;
    const int num_vectors = L - tau * (p - 1);
    // 直接分配连续的二维数据内存(按行优先存储)
    double* data = (double*)malloc(num_vectors * p * sizeof(double));
    
    if (!data) {
        // 内存分配失败时设置Python异常
        PyErr_NoMemory();
        return NULL;
    }

    for (i = 0; i < num_vectors; ++i) {
        for (j = 0; j < p; ++j) {
            data[i * p + j] = ts[i + j * tau];
        }
    }

    return data;
}

修改后的包装器

static PyObject* delay_vectors_wrapper(PyObject* self, PyObject* args) {
    PyObject *input_array;
    PyArrayObject *input_array_np;
    int tau, p;

    if (!PyArg_ParseTuple(args, "Oii", &input_array, &tau, &p)) {
        return NULL;
    }

    input_array_np = (PyArrayObject *)PyArray_FROM_OTF(input_array, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);
    if (!input_array_np) {
        return NULL;
    }

    if (PyArray_NDIM(input_array_np) != 1) {
        PyErr_SetString(PyExc_ValueError, "Input array must be 1-dimensional.");
        Py_DECREF(input_array_np);
        return NULL;
    }
    
    const int L = PyArray_DIM(input_array_np, 0);
    double* ts = (double*)PyArray_DATA(input_array_np);

    const int num_vectors = L - tau * (p - 1);
    double* result_data = delay_vectors(ts, L, tau, p);
    if (!result_data) {
        Py_DECREF(input_array_np);
        return NULL;
    }

    const npy_intp dims[2] = {num_vectors, p};
    PyObject* output_array = PyArray_SimpleNewFromData(2, dims, NPY_DOUBLE, result_data);
    
    // 让NumPy接管数据内存的所有权,GC时自动释放
    PyArray_ENABLEFLAGS((PyArrayObject*)output_array, NPY_ARRAY_OWNDATA);

    Py_DECREF(input_array_np);
    return output_array;
}

方案2:添加自定义清理函数(保留原C函数)

如果不想修改原C函数,可以通过PyCapsule创建持有指针数组的对象,并绑定到NumPy数组上,让Python GC在回收数组时同时释放两块内存:

步骤1:定义清理函数

static void delay_vectors_cleanup(void* ptr) {
    double** v_i_list = (double**)ptr;
    if (v_i_list) {
        free(v_i_list[0]); // 释放数据块
        free(v_i_list);    // 释放指针数组
    }
}

步骤2:修改包装器

static PyObject* delay_vectors_wrapper(PyObject* self, PyObject* args) {
    PyObject *input_array;
    PyArrayObject *input_array_np;
    int tau, p;

    if (!PyArg_ParseTuple(args, "Oii", &input_array, &tau, &p)) {
        return NULL;
    }

    input_array_np = (PyArrayObject *)PyArray_FROM_OTF(input_array, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);
    if (!input_array_np) {
        return NULL;
    }

    if (PyArray_NDIM(input_array_np) != 1) {
        PyErr_SetString(PyExc_ValueError, "Input array must be 1-dimensional.");
        Py_DECREF(input_array_np);
        return NULL;
    }
    
    const int L = PyArray_DIM(input_array_np, 0);
    double* ts = (double*)PyArray_DATA(input_array_np);

    double** result = delay_vectors(ts, L, tau, p);
    if (!result) {
        Py_DECREF(input_array_np);
        return NULL;
    }

    const npy_intp dims[2] = {L - tau * (p - 1), p};
    // 注意这里传入的是实际数据块指针result[0]
    PyObject* output_array = PyArray_SimpleNewFromData(2, dims, NPY_DOUBLE, result[0]);
    if (!output_array) {
        free(result[0]);
        free(result);
        Py_DECREF(input_array_np);
        return NULL;
    }

    // 创建Capsule对象持有指针数组,绑定清理函数
    PyObject* capsule = PyCapsule_New(result, NULL, delay_vectors_cleanup);
    if (!capsule) {
        Py_DECREF(output_array);
        free(result[0]);
        free(result);
        Py_DECREF(input_array_np);
        return NULL;
    }

    // 将Capsule设置为NumPy数组的base对象,GC时自动触发清理
    if (PyArray_SetBaseObject((PyArrayObject*)output_array, capsule) < 0) {
        Py_DECREF(capsule);
        Py_DECREF(output_array);
        free(result[0]);
        free(result);
        Py_DECREF(input_array_np);
        return NULL;
    }

    Py_DECREF(input_array_np);
    return output_array;
}

验证

修改后再次运行循环测试,内存占用将不再持续增长。用valgrind检测也会显示无内存泄漏。

内容的提问来源于stack exchange,提问作者Mannystein

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 04:17:03