循环中使用numpy加载时内存溢出

5 投票
2 回答
6606 浏览
提问于 2025-04-17 12:51

在处理npz文件时,循环加载会导致内存溢出(这取决于文件列表的长度)。

以下这些方法似乎都没有帮助:

  1. 删除存储文件数据的变量。

  2. 使用mmap。

  3. 调用gc.collect()(垃圾回收)。

下面的代码应该能重现这个问题:

import numpy as np

# generate a file for the demo
X = np.random.randn(1000,1000)
np.savez('tmp.npz',X=X)


# here come the overflow:
for i in xrange(1000000):
    data = np.load('tmp.npz')
    data.close()  # avoid the "too many files are open" error

在我的实际应用中,循环处理的是一个文件列表,而内存溢出超过了24GB!请注意,这个问题是在ubuntu 11.10上尝试的,使用的是numpy版本1.5.1和1.6.0。

我在numpy的2048号问题上提交了报告,但我觉得这个问题可能对更多人有帮助,所以我也在这里发帖(而且,我不确定这是否是个bug,可能是我编程不当造成的)。

解决方案(由HYRY提供):

命令

del data.f

应该在命令

data.close()

之前执行。有关更多信息和解决方法,请阅读HYRY的详细回答。

2 个回答

-1

我找到的解决办法是: (python版本为3.8,numpy版本为1.18.5)

import gc # import garbage collector interface

for i in range(1000):
   data = np.load('tmp.npy')

   # process data

   del data
   gc.collect()

4

我觉得这可能是个bug,也许我找到了个解决办法:调用“del data.f”。

for i in xrange(10000000):
    data = np.load('tmp.npz')
    del data.f
    data.close()  # avoid the "too many files are open" error

要发现这种内存泄漏,你可以使用以下代码:

import numpy as np
import gc
# here come the overflow:
for i in xrange(10000):
    data = np.load('tmp.npz')
    data.close()  # avoid the "too many files are open" error

d = dict()
for o in gc.get_objects():
    name = type(o).__name__
    if name not in d:
        d[name] = 1
    else:
        d[name] += 1

items = d.items()
items.sort(key=lambda x:x[1])
for key, value in items:
    print key, value

在测试程序之后,我创建了一个字典,并在gc.get_objects()中统计对象数量。这里是输出结果:

...
wrapper_descriptor 1382
function 2330
tuple 9117
BagObj 10000
NpzFile 10000
list 20288
dict 21001

从结果来看,我们知道BagObj和NpzFile有问题。找到相关代码:

class NpzFile(object):
    def __init__(self, fid, own_fid=False):
        ...
        self.zip = _zip
        self.f = BagObj(self)
        if own_fid:
            self.fid = fid
        else:
            self.fid = None

    def close(self):
        """
        Close the file.

        """
        if self.zip is not None:
            self.zip.close()
            self.zip = None
        if self.fid is not None:
            self.fid.close()
            self.fid = None

    def __del__(self):
        self.close()

class BagObj(object):
    def __init__(self, obj):
        self._obj = obj
    def __getattribute__(self, key):
        try:
            return object.__getattribute__(self, '_obj')[key]
        except KeyError:
            raise AttributeError, key

NpzFile有一个del()方法,NpzFile.f是一个BagObj,而BagObj._obj又指向NpzFile,这就形成了一个循环引用,导致NpzFile和BagObj都无法被回收。这里有一些Python文档中的解释:http://docs.python.org/library/gc.html#gc.garbage

所以,要打破这个循环引用,需要调用“del data.f”。

撰写回答