如何编写内存高效的Python程序?
有人说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 个回答
考虑用xrange()代替range(),因为我觉得xrange是一个生成器,而range()会把整个列表都展开。
我建议要么不要把整个文件都读到内存里,要么就不要把整个解压后的结构都放在内存里。
现在你同时把这两者都放在内存里,这样会占用很多空间。所以你至少有两份数据在内存中,还有一些额外的信息。
而且最后一行
f.write(os.linesep.join(data))
可能意味着你暂时在内存中有第三份副本(一个包含整个输出文件的大字符串)。
所以我觉得你这样做效率不高,因为你同时把整个输入文件、整个输出文件和大量中间数据都放在内存里。
用生成器来解析数据是个不错的主意。可以考虑在生成每条记录后就把它写出去(这样就可以丢掉,内存也能被重新利用),或者如果这样写入请求太多,可以每次批量处理,比如一次处理100行。
同样,读取响应也可以分块进行。因为它们是固定的记录,这样做应该比较简单。
这里主要的问题就是上面提到的range()这个调用。它会创建一个包含1500万个元素的列表,这样会占用你200MB的内存,而如果你有15个进程,那就会用掉3GB的内存。
另外,不要把整个15MB的文件一次性读入data(),而是应该从响应中一点一点地读取。把这15MB的数据放到一个变量里,会比逐步读取占用更多的内存。
你可以考虑只提取数据,直到输入数据用完为止,然后把提取的数据量和最开始的字节数进行比较。这样你就不需要使用range()或xrange()了。这样做在Python中更符合习惯。:)
就像其他人说的,你至少需要做以下两个改动:
不要用
range
创建一个很大的整数列表# use xrange for i in xrange(0, count): # UNPACK FIXED LENGTH OF BINARY DATA HERE yield (field1, field2, field3)
不要一次性创建一个很大的字符串作为整个文件的内容
# 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()