python - yield使用浅析 - 为什么使用`()`的生成器需要大量内存?



python生成器输出 (2)

问题

假设我想为所有小于20000000数字找到n**2

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

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. 有没有相当于我的第三个选项的内置函数?

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

    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
    

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

  2. 因此, xrange(20000000)在返回惰性迭代时极大地提高了内存使用率。 这实际上是内置内存有效的方式来迭代一系列反映您的第三个版本的数字(具有startstopstep的额外灵活性):

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

    在Python 3中, range本质上是xrange曾经在Python 2中使用的range 。但是,Python 3 range对象具有Python 2的xrange没有的一些很好的功能,如O(1)切片,包含等。

一些参考:

  • Python2 xrange文档
  • Python3范围文档
  • - “你是否总是喜欢xrange()超越范围()?”
  • Martijn Pieters出色地回答“为什么Python 3中的100亿个范围(1000000000000001)如此之快?”

Answer #2

1.-对象必须在内存中创建,所以在你的第二个解决方案中,生成器是创建但没有计算 ,但仍然有内存,python可能保留一些内存,使其计算有效,我们不知道解释器魔术,还注意到range功能创建了从0200000的完整列表,所以实际上你仍然在内存中构建该列表。

2.-您可以使用itertool.imap

squares = itertools.imap(lambda x: x**2, xrange(200000))




generator