Python 中使用 Cython 导致的内存泄露:解决方法和代码示例

博主正在使用 Cython 开发一个 Python 扩展模块,该模块封装了一个用 C++ 编写的类。在使用该模块处理数据时,程序在终止前大约四分之一的概率会出现段错误 (Segmentation Fault),但所有数据都已正确处理。

博主已经尝试使用 GDB 调试,但发现段错误发生在 Python 代码之外。并且,无论在 ipython 中运行代码、更改解释器/ipython,还是将 Cython 从 1.9.1 降级到 1.6,问题依然存在。

解决方案

通过 Valgrind 分析,博主发现问题并非 C++ 代码中的内存泄露,而是 Cython 代码中的内存泄露。博主在 Cython 代码中发现了一段存在问题的代码:

def split_props(np.ndarray[fptype, ndim=1, mode="c"] x,
                   np.ndarray[fptype, ndim=1, mode="c"] y,
                   np.ndarray[fptype, ndim=1, mode="c"] ylines):

cdef np.ndarray[fptype, ndim = 1, mode = "c"] areas = np.zeros((ylines.shape[0] + 1), dtype=np.float64, order="c")
cdef np.ndarray[fptype, ndim = 1, mode = "c"] static_moments_x = np.zeros((ylines.shape[0] + 1), dtype=np.float64, order="c")
cdef np.ndarray[fptype, ndim = 1, mode = "c"] inertia_moments_xx = np.zeros((ylines.shape[0] + 1), dtype=np.float64, order="c")

cdef SplitPolygon * SPINSTANCE = new SplitPolygon(100, 100)

SPINSTANCE.split_props(& x[0],  # = <fptype *> x.data
                         & y[0],
                         x.shape[0],
                         & ylines[0],
                         ylines.shape[0],
                         & areas[0],
                         & static_moments_x[0],
                         & inertia_moments_xx[0]
                         )
del SPINSTANCE
return areas, static_moments_x, inertia_moments_xx

在这段代码中,博主发现问题出在 SplitPolygon.split_props() 函数的调用上。该函数将 xyylines 数组作为参数,并在内部对这些数组进行操作。然而,博主在传递这些数组时并没有正确地遵守 SplitPolygon 类的规范,导致 SplitPolygon.split_props() 函数产生了未定义的行为。

在这种情况下,未定义的行为是 SplitPolygon.split_props() 函数在 xyylines 数组的边界之外写入数据。这些数组是由 Numpy 使用 zeros() 函数分配的,因此损坏的内存位于它们的附近。在不同的运行中,损坏的内存可能属于一个关键的 Python 对象,从而导致段错误,也可能不属于任何关键对象,从而导致程序正常运行。

代码例子

博主提供了两个 Cython 函数的代码示例,第一个函数 split_props() 在调用 C++ 代码时存在问题,第二个函数 SplitCirc() 则没有这个问题:

# 有问题的函数
def split_props(np.ndarray[fptype, ndim=1, mode="c"] x,
                   np.ndarray[fptype, ndim=1, mode="c"] y,
                   np.ndarray[fptype, ndim=1, mode="c"] ylines):

cdef np.ndarray[fptype, ndim = 1, mode = "c"] areas = np.zeros((ylines.shape[0] + 1), dtype=np.float64, order="c")
cdef np.ndarray[fptype, ndim = 1, mode = "c"] static_moments_x = np.zeros((ylines.shape[0] + 1), dtype=np.float64, order="c")
cdef np.ndarray[fptype, ndim = 1, mode = "c"] inertia_moments_xx = np.zeros((ylines.shape[0] + 1), dtype=np.float64, order="c")

cdef SplitPolygon * SPINSTANCE = new SplitPolygon(100, 100)

SPINSTANCE.split_props(& x[0],  # = <fptype *> x.data
                         & y[0],
                         x.shape[0],
                         & ylines[0],
                         ylines.shape[0],
                         & areas[0],
                         & static_moments_x[0],
                         & inertia_moments_xx[0]
                         )
del SPINSTANCE
return areas, static_moments_x, inertia_moments_xx


# 没有问题的函数
def SplitCirc(np.ndarray[fptype, ndim=1, mode="c"] ycenters,
               np.ndarray[fptype, ndim=1, mode="c"] radii,
               np.ndarray[fptype, ndim=1, mode="c"] ylines):

cdef np.ndarray[fptype, ndim = 1, mode = "c"] areas = np.zeros((len(ylines) + 1), dtype=np.float64, order="c")
cdef np.ndarray[fptype, ndim = 1, mode = "c"] static_moments_x = np.zeros((len(ylines) + 1), dtype=np.float64, order="c")
cdef np.ndarray[fptype, ndim = 1, mode = "c"] inertia_moments_xx = np.zeros((len(ylines) + 1), dtype=np.float64, order="c")

split_circles(& ycenters[0], & radii[0], len(ycenters),
              & ylines[0], len(ylines),
              & areas[0], & static_moments_x[0], & inertia_moments_xx[0])
return areas, static_moments_x, inertia_moments_xx

博主通过修复 split_props() 函数中对 SplitPolygon.split_props() 函数的调用,解决了内存泄露问题。博主也提供了另一个 SplitCirc() 函数的代码示例,该函数没有内存泄露问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值