如何编写内存高效的Python程序?

时间:2009-11-02 06:08:09

标签: python memory memory-management

据说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

8 个答案:

答案 0 :(得分:11)

正如其他人所说,至少需要进行以下两项修改:

  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()
    
  3. 更好的是,您可以将文件写为:

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

答案 1 :(得分:8)

这里的主要罪魁祸首是如上所述的range()调用。它将创建一个包含1500万成员的列表,这将占用200 MB的内存,并且有15个进程,即3GB。

但也不要将整个15MB文件读入data(),从响应中逐位读取。将这15MB的内容粘贴到一个变量中会比响应中的一点一点地消耗15MB的内存。

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

答案 2 :(得分:6)

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

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

目前你保持记忆,同时,这将是相当大的。所以你在内存中至少有两份你的数据副本,加上一些元数据。

也是最后一行

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

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

所以我说你以非常低效的方式做这件事,将整个输入文件,整个输出文件和相当数量的中间数据保存在内存中。

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

同样,阅读响应可以在块中完成。由于它们是固定记录,因此应该相当容易。

答案 3 :(得分:5)

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

答案 4 :(得分:2)

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

Python的内存效率不高。它不是为此而建的。

答案 5 :(得分:2)

如果将其转换为列表解析,则可以在编译的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的条目,加上单独的项目引用被跳过,加上迭代是在编译的代码中完成的。

答案 6 :(得分:2)

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

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

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

答案 7 :(得分:1)

确保在线程停止后删除它们。 (使用del