为什么生成器表达式需要大量内存?

时间:2016-05-11 08:06:41

标签: python python-2.7 generator

问题

我们假设我想找到n**2以查找小于20000000的所有数字。

我测试的所有三种变体的常规设置:

import time, psutil, gc

gc.collect()
mem_before = psutil.virtual_memory()[3]
time1 = time.time()

# (comprehension, generator, function)-code comes here

time2 = time.time()
mem_after =  psutil.virtual_memory()[3]

print "Used Mem = ", (mem_after - mem_before)/(1024**2)  # convert Byte to Megabyte
print "Calculation time = ", time2 - time1

计算这些数字的三个选项:

1。创建通过理解列表:

x = [i**2 for i in range(20000000)]

这真的很慢且耗时:

Used Mem =  1270  # Megabytes
Calculation time =  33.9309999943  # Seconds

2。使用'()'创建生成器:

x = (i**2 for i in range(20000000))

它比选项1快得多,但仍然使用大量内存:

Used Mem =  611 
Calculation time =  0.278000116348 

第3。定义生成器函数(最有效):

def f(n):
    i = 0
    while i < n:
        yield i**2
        i += 1
x = f(20000000)

它的消费:

Used Mem =  0
Calculation time =  0.0

问题是:

  1. 第一种和第二种解决方案有什么区别?使用()创建一个生成器,为什么它需要大量内存?
  2. 是否有与我的第三个选项相当的内置函数?

2 个答案:

答案 0 :(得分:53)

  1. 正如其他人在评论中指出的那样,range在Python 2中创建了一个list。因此,它不是生成器本身耗尽内存,而是{{ 1}}生成器使用:

    range

    这也解释了为什么你的第二个版本(生成器表达式)使用第一个版本(列表推导)的大约一半的内存,因为第一个版本构建两个列表(对于基础和正方形)而第二个版本仅构建一个基地名单。

  2. 因此,
  3. x = (i**2 for i in range(20000000)) # builds a 2*10**7 element list, not for the squares , but for the bases >>> sys.getsizeof(range(100)) 872 >>> sys.getsizeof(xrange(100)) 40 >>> sys.getsizeof(range(1000)) 8720 >>> sys.getsizeof(xrange(1000)) 40 >>> sys.getsizeof(range(20000000)) 160000072 >>> sys.getsizeof(xrange(20000000)) 40 可以大大提高内存使用率,因为它返回了一个惰性迭代。这实际上是内置内存有效的方式来迭代反映您的第三个版本的一系列数字(xrange(20000000)startstop的灵活性增加:

    step

    在Python 3中,x = (i**2 for i in xrange(20000000)) 本质上是{2}在Python 2中的含义。 但是,Python 3 range对象具有Python 2 xrange没有的一些很好的功能,如range切片,包含等等。

  4. 一些参考文献:

答案 1 :(得分:1)

1.-必须在内存中创建对象,因此在第二解决方案中,生成器已创建但未计算,但仍有内存,python可能为其计算保留一些内存效率,我们不知道解释器魔法,还注意range功能创建从0到{{1}的完整列表实际上,你仍然在内存中构建该列表。

2.-您可以使用itertool.imap

200000