【问题标题】:memory use in large data-structures manipulation/processing大型数据结构操作/处理中的内存使用
【发布时间】:2010-10-05 12:02:42
【问题描述】:

我有许多大型(~100 Mb)文件,我经常处理这些文件。虽然我试图在处理过程中删除不需要的数据结构,但内存消耗有点太高了。我想知道是否有一种方法可以有效地处理大数据,例如:

def read(self, filename):
    fc = read_100_mb_file(filename)
    self.process(fc)
def process(self, content):
    # do some processing of file content

是否存在重复的数据结构?使用 self.fc 这样的类范围属性不是更节省内存吗?

什么时候应该使用垃圾回收?我知道 gc 模块,但是例如在我 del fc 之后调用它吗?

更新
p.s. 100 Mb 本身不是问题。但是浮点转换,进一步处理显着增加了工作集和虚拟大小(我在 Windows 上)。

【问题讨论】:

  • 文件中有什么?处理是做什么的?
  • 逗号分隔的时间序列,我简化为一些可理解的汇总变量
  • 你能不能说的更具体一些,可以发一个小例子吗?
  • scikits.timeseries.lib.movi​​ng_funcs 包含“移动窗口函数”,允许对任意大数据使用固定数量的内存(只是当前跨度)。

标签: python data-structures memory-leaks garbage-collection


【解决方案1】:

我建议查看presentation by David Beazley 在 Python 中使用生成器。这种技术允许您快速处理大量数据并进行复杂的处理,而且不会占用大量内存。 IMO,诀窍不是尽可能有效地在内存中保存大量数据;诀窍是避免同时将大量数据加载到内存中。

【讨论】:

  • 啊,我一看到问题就跳进去回答,并附上 Beazley 资料的链接,并看到你已经给出了答案。哦,好吧,必须给你投票+1!只是希望我能给它超过 +1。
【解决方案2】:

在开始使用垃圾收集器之前,您可以通过使用内存映射文件对象来避免将整个文件加载到内存中的 100mb 命中。请参阅mmap 模块。

【讨论】:

  • 100 Mb 就好了,当它达到 1.7 Gb 的虚拟内存时问题就开始了
  • 哎呀!这听起来更像是你挂在很多东西的引用上,所以垃圾收集器无法清理它们。如果您在处理类中保存对中间数据的引用,就会发生这种情况。
【解决方案3】:

不要一次读取整个 100 meg 文件。使用流一次处理一点。查看这篇关于处理大型 csv 和 xml 文件的博客文章。 http://lethain.com/entry/2009/jan/22/handling-very-large-csv-and-xml-files-in-python/

这是文章中的代码示例。

from __future__ import with_statement # for python 2.5

with open('data.in','r') as fin:
    with open('data.out','w') as fout:
        for line in fin:
            fout.write(','.join(line.split(' ')))

【讨论】:

  • 它在代码方面似乎没有扩展,我不需要重新排列位,涉及更多处理
  • 一旦你解析了一个细节行并完成了你的减少计算,确保你没有挂在任何通过解析细节创建的对象上。 Python GC 是基于引用的。只要有对对象的引用,它就不会被 GC。
  • 只是添加。如果您有两个相互引用的对象,除非其中一个对象放弃对另一个对象的引用,否则它们将永远不会被垃圾回收。如果您发现内存使用量激增并且您的对象应该超出范围,请检查这种循环引用。
  • @Sam Corder:循环垃圾回收早已被添加到 Python 中。
【解决方案4】:

因此,根据您的 cmets,我假设您的文件如下所示:

item1,item2,item3,item4,item5,item6,item7,...,itemn

通过重复应用某些组合功能,您都可以将其简化为单个值。作为一种解决方案,一次只读取一个值:

def read_values(f):
    buf = []
    while True:
        c = f.read(1)
        if c == ",":
            yield parse("".join(buf))
            buf = []
        elif c == "":
            yield parse("".join(buf))
            return
        else:
            buf.append(c)

with open("some_file", "r") as f:
     agg = initial
     for v in read_values(f):
         agg = combine(agg, v)

这样,内存消耗保持不变,除非agg 及时增长。

  1. 提供initialparsecombine 的适当实现
  2. 不要逐字节读取文件,而是在固定缓冲区中读取,从缓冲区中解析并根据需要读取更多内容
  3. 这基本上是内置的reduce 函数所做的,但为了清楚起见,我在这里使用了一个显式的 for 循环。使用reduce 也是一样的:

    with open("some_file", "r") as f:
        agg = reduce(combine, read_values(f), initial)
    

我希望我正确地解释了您的问题。

【讨论】:

  • 对不起,如果我说得有点笨拙,但减少我的意思是“从 100 Mb 制作 32 Kb”
  • 不,我不是那个意思,我指的是 reduce 内置函数。
  • 顺便说一句,f.read() 在您的代码中应该是 f.read(1)。和 open("somefile", r) -> open("somefile", "r").
  • @J.F.:啊,不用测试编码的乐趣。我实际上已经尝试了代码并在那里使用了 f.read(1)。
【解决方案5】:

首先,不要碰垃圾收集器。这不是问题,也不是解决方案。

听起来您遇到的真正问题根本不是文件读取,而是您在处理文件时分配的数据结构。 考虑使用 del 删除处理过程中不再需要的结构。此外,您可以考虑使用 marshal 将一些已处理的数据转储到磁盘,同时处理接下来的 100mb 输入文件。

对于文件读取,您基本上有两种选择:作为流的 unix 样式文件,或内存映射文件。对于基于流的文件,默认的 python 文件对象已经被缓冲了,所以最简单的代码也可能是最高效的:

使用 open("filename", "r") 作为 f: 对于 f 中的行: # 对一行文件做一些事情

或者,您可以使用 f.read([size]) 来读取文件的块。但是,通常您这样做是为了获得 CPU 性能,通过对脚本的处理部分进行多线程处理,以便您可以同时读取和处理。但这对内存使用没有帮助;事实上,它使用了更多的内存。

另一个选项是 mmap,如下所示:

使用 open("filename", "r+") 作为 f: 地图 = mmap.mmap(f.fileno(), 0) line = map.readline() 而线!='': # 处理一行 line = map.readline()

这有时优于流,但也不会提高内存使用率。

【讨论】:

    【解决方案6】:

    在您的示例代码中,数据存储在fc 变量中。如果您不保留对fc 的引用,则当read 方法结束时,您的整个文件内容将从内存中删除。

    如果不是,则您在某处保留了参考。可能在read_100_mb_file 中创建引用,也可能在process 中创建。如果没有引用,CPython 实现将几乎立即释放它。

    有一些工具可以帮助您找到此参考的位置,guppydowserpysizer...

    【讨论】:

      猜你喜欢
      • 2012-11-27
      • 2010-10-20
      • 2010-12-20
      • 1970-01-01
      • 1970-01-01
      • 2016-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多