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

10 投票
8 回答
13168 浏览
提问于 2025-04-15 15:35

有人说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 个回答

6

考虑用xrange()代替range(),因为我觉得xrange是一个生成器,而range()会把整个列表都展开。

我建议要么不要把整个文件都读到内存里,要么就不要把整个解压后的结构都放在内存里。

现在你同时把这两者都放在内存里,这样会占用很多空间。所以你至少有两份数据在内存中,还有一些额外的信息。

而且最后一行

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

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

所以我觉得你这样做效率不高,因为你同时把整个输入文件、整个输出文件和大量中间数据都放在内存里。

用生成器来解析数据是个不错的主意。可以考虑在生成每条记录后就把它写出去(这样就可以丢掉,内存也能被重新利用),或者如果这样写入请求太多,可以每次批量处理,比如一次处理100行。

同样,读取响应也可以分块进行。因为它们是固定的记录,这样做应该比较简单。

9

这里主要的问题就是上面提到的range()这个调用。它会创建一个包含1500万个元素的列表,这样会占用你200MB的内存,而如果你有15个进程,那就会用掉3GB的内存。

另外,不要把整个15MB的文件一次性读入data(),而是应该从响应中一点一点地读取。把这15MB的数据放到一个变量里,会比逐步读取占用更多的内存。

你可以考虑只提取数据,直到输入数据用完为止,然后把提取的数据量和最开始的字节数进行比较。这样你就不需要使用range()或xrange()了。这样做在Python中更符合习惯。:)

12

就像其他人说的,你至少需要做以下两个改动:

  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()

撰写回答