【问题标题】:How to write a memory efficient Python program?如何编写内存高效的 Python 程序?
【发布时间】:2010-12-12 04:37:49
【问题描述】:

据说Python会自动管理内存。我很困惑,因为我有一个 Python 程序一直使用超过 2GB 的内存。

这是一个简单的多线程二进制数据下载器和解包器。

def GetData(url):
    req = urllib2.Request(url)
    response = urllib2.urlopen(req)
    data = response.read() // data size is about 15MB
    response.close()
    count = struct.unpack("!I", data[:4])
    for i in range(0, count):
        UNPACK FIXED LENGTH OF BINARY DATA HERE
        yield (field1, field2, field3)

class MyThread(threading.Thread):
    def __init__(self, total, daterange, tickers):
        threading.Thread.__init__(self)

    def stop(self):
        self._Thread__stop()

    def run(self):
        GET URL FOR EACH REQUEST
        data = []
        items = GetData(url)
        for item in items:
            data.append(';'.join(item))
        f = open(filename, 'w')
        f.write(os.linesep.join(data))
        f.close()

有 15 个线程正在运行。每个请求获取 15MB 的数据并将其解压缩并保存到本地文本文件。这个程序怎么会消耗超过 2GB 的内存?在这种情况下我需要做任何内存回收工作吗?如何查看每个对象或函数使用了多少内存?

对于如何让 python 程序在内存高效模式下运行的所有建议或技巧,我将不胜感激。

编辑:这是“cat /proc/meminfo”的输出

MemTotal:        7975216 kB
MemFree:          732368 kB
Buffers:           38032 kB
Cached:          4365664 kB
SwapCached:        14016 kB
Active:          2182264 kB
Inactive:        4836612 kB

【问题讨论】:

  • 第一个问题是:你有多少物理内存可用?可能只是因为 Python 认为它不值得清理,因为仍然有 1GB 的可用物理内存。
  • @Matthew,看起来只有 730MB 的 8GB RAM 可用。
  • 尝试生成 15 个进程 - 这种方式消耗了多少内存?我猜得少得多,我猜真正的罪魁祸首是线程的东西(我写了一个非常简单的 TCP 回显服务器,使用多线程在 2 小时运行后占用 100% CPU - 用扭曲修复,从不费心研究线程缺乏表现)。

标签: python memory memory-management


【解决方案1】:

正如其他人所说,您至少需要以下两项更改:

  1. 不要使用range 创建大量整数列表

    # use xrange
    for i in xrange(0, count):
        # UNPACK FIXED LENGTH OF BINARY DATA HERE
        yield (field1, field2, field3)
    
  2. 不要创建一个巨大的字符串作为一次写入的完整文件主体

    # use writelines
    f = open(filename, 'w')
    f.writelines((datum + os.linesep) for datum in data)
    f.close()
    

更好的是,您可以将文件写为:

    items = GetData(url)
    f = open(filename, 'w')
    for item in items:
        f.write(';'.join(item) + os.linesep)
    f.close()

【讨论】:

    【解决方案2】:

    确保在线程停止后将其删除。 (使用del

    【讨论】:

      【解决方案3】:

      有两个明显的地方可以将大型数据对象保存在内存中(GetData() 中的data 变量和MyThread.run() 中的data - 这两个将占用大约 500Mb)并且可能还有其他地方在跳过代码。两者都很容易使内存高效。使用response.read(4) 而不是一次读取整个响应,并在UNPACK FIXED LENGTH OF BINARY DATA HERE 后面的代码中以相同的方式执行此操作。将MyThread.run() 中的data.append(...) 更改为

      if not first:
          f.write(os.linesep)
      f.write(';'.join(item))
      

      这些更改将为您节省大量内存。

      【讨论】:

        【解决方案4】:

        这里的罪魁祸首是上面提到的 range() 调用。它将创建一个包含 1500 万成员的列表,这将占用您 200 MB 的内存,以及 15 个进程,即 3GB。

        但也不要将整个 15MB 文件读入 data(),从响应中逐位读取。将这 15MB 存入一个变量会比从响应中逐位读取更多的内存占用 15MB。

        您可能只想简单地提取数据,直到 indata 用完为止,并将提取的数据计数与第一个字节所说的数据进行比较。那么你既不需要 range() 也不需要 xrange()。对我来说似乎更pythonic。 :)

        【讨论】:

        • 其实这里的变量计数并不接近1500万的范围,因为每个二进制数据块都是30字节,所以它不会创建一个包含1500万个元素的列表。
        • 啊,我明白了。那么,它仍然会有所帮助,就像不将所有数据读入data一样。
        【解决方案5】:

        考虑使用 xrange() 代替 range(),我相信 xrange 是一个生成器,而 range() 扩展了整个列表。

        我会说要么不要将整个文件读入内存,要么不要将整个解压后的结构保存在内存中。

        目前您将两者都保存在内存中,同时,这将是相当大的。因此,您的内存中至少有两个数据副本,以及一些元数据。

        也是最后一行

            f.write(os.linesep.join(data))
        

        实际上可能意味着您在内存中临时获得了第三个副本(包含整个输出文件的大字符串)。

        所以我会说你这样做的效率很低,一次将整个输入文件、整个输出文件和相当数量的中间数据保存在内存中。

        使用生成器来解析它是一个不错的主意。考虑在生成每条记录后将其写出(然后可以将其丢弃并重新使用内存),或者如果这导致过多的写入请求,则将它们一次批处理成 100 行。

        同样,读取响应可以分块完成。由于它们是固定记录,这应该相当容易。

        【讨论】:

          【解决方案6】:

          如果您将其转换为列表推导式,您可以在编译后的 C 代码中完成更多工作:

          data = []
          items = GetData(url)
          for item in items:
              data.append(';'.join(item))
          

          到:

          data = [';'.join(items) for items in GetData(url)]
          

          这实际上与您的原始代码略有不同。在您的版本中,GetData 返回一个 3 元组,它以项目的形式返回。然后迭代这个三元组,并为其中的每个项目附加 ';'.join(item)。这意味着您会为从 GetData 读取的每个三元组添加 3 个条目,每个条目都是 ';'.join'ed。如果项目只是字符串,那么 ';'.join 会给你一个字符串,每个其他字符都是一个 ';' - 那就是 ';'.join("ABC") 将返回 "A;B;C"。我认为您实际上想要 是将每个三元组保存回数据列表作为三元组的 3 个值,用分号分隔。这就是我的版本生成的。

          这也可能对您最初的内存问题有所帮助,因为您不再创建尽可能多的 Python 值。请记住,Python 中的变量比 C 等语言中的变量具有更多开销。由于每个值本身都是一个对象,并且将每个名称引用的开销添加到该对象,您可以轻松扩展理论存储需求数倍。在您的情况下,读取 15Mb X 15 = 225Mb + 每个三元组的每个项目作为字符串条目存储在数据列表中的开销可能会迅速增长到您观察到的 2Gb 大小。至少,我的数据列表版本中只有 1/3 的条目,加上单独的项目引用被跳过,而且迭代是在编译代码中完成的。

          【讨论】:

            【解决方案7】:

            最后一行肯定是f.close()?那些尾随的括号有点重要。

            【讨论】:

            • 是的,它是 f.close()。这是原始帖子中的错字。
            【解决方案8】:

            您可以通过不从 TCP 连接读取所有 15MB 的内存,而是在读取每一行时处理每一行,从而提高该程序的内存效率。当然,这将使远程服务器等待您,但这没关系。

            Python 的内存效率不是很高。它不是为此而设计的。

            【讨论】:

              猜你喜欢
              • 2011-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-04-04
              • 2012-11-04
              • 2017-01-26
              • 1970-01-01
              • 2020-01-21
              相关资源
              最近更新 更多