调试Python/NumPy内存泄漏问题

5

我正在尝试找出一个Python/NumPy程序中的内存泄漏源,该程序使用C/Cython扩展和multiprocessing

每个子进程处理一组图像,并将输出数组(通常约200-300MB)通过Queue发送到主进程。这是一个相当标准的映射/规约设置。

可以想象,对于如此大的数组,内存泄漏可能会变得非常严重,并且当多个进程需要的只有5-6GB时,它们很容易快乐地占用20GB RAM,这是令人恼火的。

  • 我尝试通过Valgrind运行Python的调试版本,并四次检查我的扩展是否存在内存泄漏,但没有发现任何问题。

  • 我已检查了我的Python代码是否存在对数组的悬空引用,并使用了NumPy的分配跟踪器来检查我的数组是否被释放。它们已经被释放了。

我最后做的事情是将GDB附加到我的一个进程上(这个坏小子现在正在运行27GB RAM并且还在增加),并将堆的大部分转储到磁盘上。令我惊讶的是,转储文件中充满了零!约7G的零值。 这是否是Python / NumPy中标准的内存分配行为?我是否错过了一些明显的东西来解释为什么会有如此多的内存用于无用之处?我该如何正确地管理内存?

编辑:记录一下,我正在运行NumPy 1.7.1和Python 2.7.3。

编辑2:我一直在使用strace监视进程,似乎它不断增加每个进程的断点(使用brk()系统调用)。

CPython是否实际上正确释放内存?C扩展,NumPy数组呢?谁决定何时调用brk(),是Python本身还是底层库(libc,...)?

以下是一个迭代(即一个输入图像集)的带有注释的样本strace日志。请注意,断点不断增加,但我确保(使用objgraph)没有有意义的NumPy数组留在Python解释器中。

# Reading .inf files with metadata
# Pretty small, no brk()
open("1_tif_all/AIR00642_1.inf", O_RDONLY) = 6
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9387fff000
munmap(0x7f9387fff000, 4096)            = 0
open("1_tif_all/AIR00642_2.inf", O_RDONLY) = 6
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9387fff000
munmap(0x7f9387fff000, 4096)            = 0
open("1_tif_all/AIR00642_3.inf", O_RDONLY) = 6
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9387fff000
munmap(0x7f9387fff000, 4096)            = 0
open("1_tif_all/AIR00642_4.inf", O_RDONLY) = 6
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9387fff000
munmap(0x7f9387fff000, 4096)            = 0

# This is where I'm starting the heavy processing
write(2, "[INFO/MapProcess-1] Shot 642: Da"..., 68) = 68
write(2, "[INFO/MapProcess-1] Shot 642: Vi"..., 103) = 103
write(2, "[INFO/MapProcess-1] Shot 642: Re"..., 66) = 66

# I'm opening a .tif image (752 x 480, 8-bit, 1 channel)
open("1_tif_all/AIR00642_3.tif", O_RDONLY) = 6
read(6, "II*\0JC\4\0", 8)               = 8
mmap(NULL, 279600, PROT_READ, MAP_SHARED, 6, 0) = 0x7f9387fbb000
munmap(0x7f9387fbb000, 279600)          = 0
write(2, "[INFO/MapProcess-1] Shot 642: Pr"..., 53) = 53

# Another .tif
open("1_tif_all/AIR00642_4.tif", O_RDONLY) = 6
read(6, "II*\0\266\374\3\0", 8)         = 8
mmap(NULL, 261532, PROT_READ, MAP_SHARED, 6, 0) = 0x7f9387fc0000
munmap(0x7f9387fc0000, 261532)          = 0
write(2, "[INFO/MapProcess-1] Shot 642: Pr"..., 51) = 51
brk(0x1aea97000)                        = 0x1aea97000

# Another .tif
open("1_tif_all/AIR00642_1.tif", O_RDONLY) = 6
read(6, "II*\0\220\253\4\0", 8)         = 8
mmap(NULL, 306294, PROT_READ, MAP_SHARED, 6, 0) = 0x7f9387fb5000
munmap(0x7f9387fb5000, 306294)          = 0
brk(0x1af309000)                        = 0x1af309000
write(2, "[INFO/MapProcess-1] Shot 642: Pr"..., 53) = 53
brk(0x1b03da000)                        = 0x1b03da000

# Another .tif
open("1_tif_all/AIR00642_2.tif", O_RDONLY) = 6
mmap(NULL, 345726, PROT_READ, MAP_SHARED, 6, 0) = 0x7f9387fab000
munmap(0x7f9387fab000, 345726)          = 0
brk(0x1b0c42000)                        = 0x1b0c42000
write(2, "[INFO/MapProcess-1] Shot 642: Pr"..., 51) = 51

# I'm done reading my images
write(2, "[INFO/MapProcess-1] Shot 642: Fi"..., 72) = 72

# Allocating some more arrays for additional variables
# Increases by about 8M at a time
brk(0x1b1453000)                        = 0x1b1453000
brk(0x1b1c63000)                        = 0x1b1c63000
brk(0x1b2473000)                        = 0x1b2473000
brk(0x1b2c84000)                        = 0x1b2c84000
brk(0x1b3494000)                        = 0x1b3494000
brk(0x1b3ca5000)                        = 0x1b3ca5000

# What are these mmap calls doing here?
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9377df1000
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9367be2000
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f93579d3000
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f93477c4000
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f93375b5000
munmap(0x7f93579d3000, 270594048)       = 0
munmap(0x7f93477c4000, 270594048)       = 0
mmap(NULL, 270594048, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f93579d3000
munmap(0x7f93375b5000, 270594048)       = 0
mmap(NULL, 50737152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9354970000
munmap(0x7f9354970000, 50737152)        = 0
brk(0x1b4cc6000)                        = 0x1b4cc6000
brk(0x1b5ce7000)                        = 0x1b5ce7000

< p >< em >编辑3:小/大numpy数组的释放处理方式是否不同?可能相关。我越来越相信,我只是分配了太多不会释放到系统的数组,因为这确实是标准行为。将尝试预先分配数组并根据需要重用它们。


你用什么来读取图像文件?我以前使用PIL的Image对象时遇到了内存泄漏问题。 - ali_m
我正在使用PyLibTiff绑定。我已经解决了这个问题,请看我的回答! - F.X.
1个回答

1

糟糕。我应该第五次检查那些C扩展。

我忘记在我从C中分配的临时NumPy数组中减少一个引用计数。该数组没有离开C代码,因此我没有看到需要释放它。

我仍然不知道为什么它没有出现在objgraph中。


2
objgraph 只能显示垃圾回收器跟踪的内容,不包括 numpy 数组。请参见:https://dev59.com/sJLea4cB1Zd3GeqP4Zpu - jlh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接