【问题标题】:Why does a generator expression need a lot of memory?为什么生成器表达式需要大量内存?
【发布时间】:2016-09-06 11:55:35
【问题描述】:

问题

假设我想为所有小于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. 是否有与我的第三个选项等效的内置函数?

【问题讨论】:

标签: python python-2.7 generator


【解决方案1】:
  1. 正如其他人在 cmets 中指出的那样,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 本质上是 Python 2 中的 xrange。 但是,Python 3 range 对象具有 Python 2 的 xrange 所没有的一些不错的功能,例如 O(1) 切片、包含等。

一些参考资料:

【讨论】:

  • 说它“应该更有效地工作更多的内存”是一个有点弱的答案恕我直言。也许解释一下range 如何在内存中创建一个列表,以及这如何影响 OP 的答案 2?
  • @DonkeyKong:文化的东西。如果它不使用大量内存,那很好,但没有理由通过引入绝对数字来称赞解决方案。或者,ned gebruddeld isch gnug g'lobt,就像 schwobaseggl 可能会说的那样。
  • @MarcusMüller 我不打算得到带绝对数字的答案,我只是觉得解释有点欠缺。虽然我不介意,但很好的答案:) +1
【解决方案2】:

1.- 对象必须在内存中创建,因此在您的 second 解决方案中,生成器是 created 但不是 computed,而是仍然有内存,python 可能会为其计算保留一些内存以提高效率,我们不知道解释器的魔法,还要注意range 函数创建了从0200000 的完整列表,所以实际上你仍在内存中构建该列表。

2.- 你可以使用itertool.imap:

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

【讨论】:

    猜你喜欢
    • 2020-12-01
    • 2017-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多