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

Linux下将Numpy传递给C代码出现内存泄漏问题求助

内存泄漏问题排查:Python调用自研C扩展(Linux专属)

问题概述

环境:Ubuntu 22.04.1 LTS + Python 3.10.6

  • 自研C版Eytzinger搜索函数单独运行无内存泄漏,但通过Python调用时出现泄漏;替换为等价Python实现后泄漏消失
  • 仅Linux系统出现该问题,MacOS下无异常
  • 额外现象:同时调用Python和C版本时,C版本返回-1;仅调用C版本即可触发Linux上的泄漏

核心代码片段

C扩展实现(对应问题中的C代码逻辑)

static PyObject *mapbufferaccel_eytzinger_search(PyObject *self, PyObject *args) {
    uint64_t target;
    PyArrayObject *arr;
    if (!PyArg_ParseTuple(args, "KO!", &target, &PyArray_Type, &arr)) {
        return NULL;
    }
    uint64_t *data = (uint64_t *) PyArray_DATA(arr);
    npy_intp N = PyArray_DIM(arr, 0);
    npy_intp mid = 0;
    while (mid < N) {
        if (data[mid] == target) {
            return PyLong_FromLongLong(mid);
        }
        mid = mid * 2 + 1 + (data[mid] < target);
    }
    return PyLong_FromLongLong(-1);
}

等价无泄漏Python实现

def eytzinger_search(target, arr):
    mid = 0
    N = len(arr)

    while mid < N:
        if arr[mid] == target:
            return mid
        mid = mid * 2 + 1 + int(arr[mid] < target)

    return -1

k = eytzinger_search(np.uint64(label), index[:, 0])

可能的原因分析

1. 整数溢出导致的内存越界访问

C代码中mid = mid * 2 + 1 + (data[mid] < target)的计算存在溢出风险:

  • npy_intp是64位整数,但当mid值足够大时,mid * 2可能溢出变为负数
  • 负数的mid仍满足mid < N(N为正整数),导致循环继续,进而访问data[mid](越界内存)
  • Linux下的glibc内存分配器对堆结构破坏更敏感,越界访问会损坏分配器元数据,引发内存泄漏;而MacOS的libmalloc对此容错性更强,或单独运行C程序时内存布局未触发明显问题

2. Python内存分配器与glibc的交互冲突

Python 3.10默认使用pymalloc作为内存分配器,而Linux下NumPy可能使用glibc的malloc分配数组内存:

  • 当C扩展直接操作NumPy数组内存时,pymalloc与glibc的内存管理边界可能出现冲突,导致部分内存无法被正确回收
  • MacOS下NumPy使用系统的libmalloc,与Python内存分配器的兼容性更好,因此无泄漏

3. NumPy切片的内存所有权问题

Python调用时传入的index[:, 0]是NumPy切片视图,而非副本:

  • Linux下NumPy视图的内存生命周期管理与MacOS存在差异,C扩展持有指针期间,Python的垃圾回收可能未正确跟踪内存,导致泄漏
  • 但Python实现无泄漏,说明核心问题不在切片本身,而是C代码与内存管理的交互

排查与修复建议

1. 修复整数溢出问题

在C代码循环中添加溢出检查,避免越界访问:

while (mid < N) {
    // 防止mid溢出为负数
    if (mid < 0) break;
    if (data[mid] == target) {
        return PyLong_FromLongLong(mid);
    }
    npy_intp next_mid = mid * 2 + 1 + (data[mid] < target);
    // 检查溢出:正常情况下next_mid应大于mid
    if (next_mid <= mid) break;
    mid = next_mid;
}

2. 用内存检测工具定位泄漏点

在Linux下使用valgrind运行Python程序,精准定位泄漏来源:

valgrind --leak-check=full --trace-children=yes python your_script.py

查看报告中是否存在C代码引发的堆内存损坏或未释放内存。

3. 调整Python内存分配器

尝试禁用pymalloc,强制使用glibc的malloc运行Python,验证是否为分配器冲突:

PYTHONMALLOC=malloc python your_script.py

若泄漏消失,说明是pymalloc与glibc的交互问题,可考虑在C扩展中显式使用PyMem_Malloc/PyMem_Free替代系统分配器。

4. 验证GIL与多线程场景

若Python代码为多线程调用C扩展,确保C代码正确处理GIL:

  • 若C代码无需并行执行,保持GIL持有(当前代码已满足)
  • 若需释放GIL,使用Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS包裹计算逻辑,避免内存管理冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 21:24:57