6

我有一个 Python 实用程序,它遍历一个tar.xz文件并处理每个单独的文件。这是一个 15MB 的压缩文件,包含 740MB 的未压缩数据。

在一台内存非常有限的特定服务器上,程序由于内存不足而崩溃。我使用objgraph来查看创建了哪些对象。事实证明,这些TarInfo实例没有被释放。主循环与此类似:

with tarfile.open(...) as tar:
    while True:
        next = tar.next()
        stream = tar.extractfile(next)
        process_stream()
        iter+=1
        if not iter%1000:
            objgraph.show_growth(limit=10)

输出非常一致:

TarInfo     2040     +1000
TarInfo     3040     +1000
TarInfo     4040     +1000
TarInfo     5040     +1000
TarInfo     6040     +1000
TarInfo     7040     +1000
TarInfo     8040     +1000
TarInfo     9040     +1000
TarInfo    10040     +1000
TarInfo    11040     +1000
TarInfo    12040     +1000

这种情况一直持续到处理完所有 30,000 个文件。

只是为了确保,我已经注释掉了创建流和处理它的行。内存使用量保持不变 - TarInfo 实例被泄露。

我使用的是 Python 3.4.1,这种行为在 Ubuntu、OS X 和 Windows 上是一致的。

4

1 回答 1

5

看起来这实际上是设计使然。TarFile对象维护TarInfo其包含在members属性中的所有对象的列表。每次调用next时,TarInfo它从存档中提取的对象都会添加到列表中:

def next(self):
    """Return the next member of the archive as a TarInfo object, when
       TarFile is opened for reading. Return None if there is no more
       available.
    """
    self._check("ra")
    if self.firstmember is not None:
        m = self.firstmember
        self.firstmember = None
        return m

    # Read the next block.
    self.fileobj.seek(self.offset)
    tarinfo = None
    ... <snip>

    if tarinfo is not None:
        self.members.append(tarinfo)  # <-- the TarInfo instance is added to members

members随着您提取更多项目,该列表将继续增长。这可以使用getmembersandgetmember方法,但对您的用例来说只是一个麻烦。似乎最好的解决方法是在members迭代时继续清除属性(如建议here):

with tarfile.open(...) as tar:
    while True:
        next = tar.next()
        stream = tar.extractfile(next)
        process_stream()
        iter+=1
        tar.members = []  # Clear members list
        if not iter%1000:
            objgraph.show_growth(limit=10)
于 2014-10-15T16:15:02.420 回答