为什么发电机更快?

4

我理解生成器比迭代器更快。我也知道可以使用for循环语法来实现生成器。例如:

    import time 


startT = time.time()


def myGen(n):
    for i in range(n):
        yield x         


def myIter(n):
    for i in range(n):
        pass

def main():
    n=100
    startT=time.time()
    myIter(n)
    print 'myIter took ', time.time() - startT

    startT=time.time()
    myGen(n)
    print 'myGen(n) took ', time.time() - startT

这只是结果的一个例子:
myIter took 0.09234782
myGen(n) took 0.017847266

由于这里使用了 for 循环语法,所以我不明白它为什么比迭代器更快。实际上,这个生成器使用了一个迭代器,因为 "for" 循环是通过迭代器实现的。如果你测试一下,你会发现这个生成器始终比迭代器更快。那么为什么呢?即使这个生成器也是在使用迭代器?

谢谢。


3
我理解生成器比迭代器更快 - 实际上并不是这样。你为什么会这么想呢?生成器是一种迭代器。 - user2357112
6
生成器不比迭代器快。生成器就是迭代器。通常,生成器函数实际上更慢,但更节省内存。 - Martijn Pieters
1
你认为这段代码比哪段代码更快并不清楚。你能在问题中提供吗? - Bill Lynch
2
同时,将变量输出到标准输出会拖慢应用程序的运行速度。 - Ozgur Vatansever
3
首先,你只运行了一次测试而不是数千次。其次,time.time() 不是一个准确的计时器。第三,生成器仅仅创建循环并进行一次迭代,而另一个函数则创建循环并 _遍历整个循环_。 - TigerhawkT3
显示剩余2条评论
2个回答

11

在你的代码中,myIter(n) 实际上是起作用的--它循环100次。

另一方面,myGen(n) 只是简单地构建生成器--仅此而已。 它不计数到100。 你所做的就是计时构建对象需要多长时间,而且你的计时方法并不可靠。 如果我们使用 timeit(这里使用 IPython 简化操作):

>>> %timeit myIter(100)
1000000 loops, best of 3: 1 µs per loop
>>> %timeit myGen(100)
10000000 loops, best of 3: 163 ns per loop
>>> %timeit myGen(10**1000)
10000000 loops, best of 3: 163 ns per loop

我们可以看到myGen(n)的时间与n无关,因为它什么也没做。实际上,我们可以从另一个角度看出你的代码从未被执行:

>>> list(myGen(100))
Traceback (most recent call last):
  File "<ipython-input-11-dd43d937402a>", line 1, in <module>
    list(myGen(100))
  File "<ipython-input-1-ba968e48e9fd>", line 3, in myGen
    yield x
NameError: name 'x' is not defined
如果我们修复这个错别字,然后尝试一种快速消耗生成器的方法,我们会得到类似以下的东西:
>>> %timeit myIter(100)
1000000 loops, best of 3: 1 µs per loop
>>> %timeit consume(myGen(100), 100)
100000 loops, best of 3: 3.44 µs per loop

而且生成器版本通常较慢,就像现在这种情况。


0
在你的代码中,你定义了两个函数:myGen和myIter,并且你想比较它们的性能。myGen是一个生成器函数,而myIter是一个普通的循环。让我们分析一下这两个函数的性能。
1. myGen是一个生成器函数,它在循环中产生一个值x。然而,在你的代码中你没有定义x是什么。我假设它是一个你在提供的代码中没有显示的变量。
2. myIter是一个普通的循环,它迭代n次,但在循环内部没有执行任何特定的操作。
就性能比较而言:
  • myIter通常会更快,因为它只是一个简单的循环,没有额外的开销来创建和管理生成器对象。

  • myGen涉及创建生成器对象并产生一个值,这需要一些额外的开销。生成器在需要惰性求值或者想要通过不一次生成所有值来节省内存时非常有用。然而,在这个特定的情况下,生成器与简单的循环相比并没有提供任何优势。

这是修改后的代码,进行了一些修正:

import time

def myGen(n, x):
    for i in range(n):
        yield x

def myIter(n):
    for i in range(n):
        pass

def main():
    n = 100
    x = 42  # Define the value to yield in myGen
    startT = time.time()
    myIter(n)
    print('myIter took', time.time() - startT)

    startT = time.time()
    list(myGen(n, x))  # Consume the generator with a list to measure its performance
    print('myGen(n) took', time.time() - startT)

if __name__ == "__main__":
    main()

在这段代码中,我们定义了值 x,它被传递给 myGen。我们还使用 list(myGen(n, x)) 来消耗生成器,以便正确地测量其性能。
一般来说,对于像您展示的简单操作,普通循环比生成器更快。生成器在处理大型数据集或者想要进行惰性评估时更有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接