7

我在摆弄 Python 的生成器和可迭代类,只是为了好玩。基本上,我想测试一些我从来都不太确定的东西:Python 中的类有一些显着的开销,如果可以的话,最好依赖实现的方法yield而不是实现迭代器协议的类。

我在 Google 中找不到关于这个主题的令人满意的解释,所以我决定使用这两个简单的脚本自己测试它们:func_iter.pyclass_iter.py

这是func_iter.py

#!/usr/bin/env python

import time  

x = 0
def create_generator(num):
    mylist = range(num)
    for i in mylist:
        yield i

t = time.time()
gen = create_generator(100000)

for i in gen:
    x = x + i

print "%.3f" % (time.time() - t)

这是class_iter.py

#!/usr/bin/env python

import time

x = 0

class Generator(object):

    def __init__(self, num):
        self.start = 0
        self.end = num

    def __iter__(self):
        return self

    def next(self):
        if self.start == self.end:
            raise StopIteration
        else:
            self.start = self.start + 1
            return self.start

t = time.time()
gen = Generator(100000)

for i in gen:
    x = x + i

print "%.3f" % (time.time() - t)

然后我在 bash 中使用它运行了它们每个 10 次(class_iter.py例如):

for i in {1..10}; do ./class_iter.py; done

以下是他们每个人的平均运行时间:

class_iter.py: 0.0864
func_iter.py: 0.0307

现在,我的问题是:

  1. 我的方法正确吗?我的比较公平吗?
  2. 如果是这样,为什么会有很大的不同?为什么class_iter.py要花将近三倍的时间func_iter.py才能运行?
  3. 如果没有,我该如何改进我的方法或提出更好的比较?

编辑:正如 Dacav 建议的那样,我也尝试func_iter.py使用xrange而不是range. 这将其平均运行时间减少到 0.0263 秒。

4

3 回答 3

6

类版本花费大量时间访问它自己的变量。每个self.whatever成本周期。如果您将您的定义__iter__为生成器并最小化实例变量的使用,则类和函数版本之间的差异可以忽略不计:

setup = """
def create_generator(num):
    mylist = range(num)
    for i in mylist:
        yield i

class Generator(object):

    def __init__(self, num):
        self.start = 0
        self.end = num

    def __iter__(self):
        return self

    def next(self):
        if self.start == self.end:
            raise StopIteration
        else:
            self.start = self.start + 1
            return self.start

class Generator2(object):

    def __init__(self, num):
        self.mylist = range(num)

    def __iter__(self):
        for i in self.mylist:
            yield i
"""

import timeit

print timeit.timeit('for p in create_generator(1000):p', setup, number=1000)
print timeit.timeit('for p in Generator(1000):p', setup, number=1000)
print timeit.timeit('for p in Generator2(1000):p', setup, number=1000)

结果:

0.158941984177
0.696810007095
0.160784959793

所以第二个生成器类几乎和函数版本一样快。

请注意,GeneratorGenerator2在示例中并不完全等效,在某些情况下您不能简单地用生成器替换“普通”迭代器(例如编组)。

于 2012-05-12T17:07:15.050 回答
1

如果您使用 python,那么您很有可能不是针对软件性能,而是更关心开发的快速和敏捷。

话虽如此,我认为比较方法是相当公平的,只要您的代码足够聪明以避免对一种解决方案产生偏见。

例如,yield基于 - 的版本的可能改进可能是删除函数并改为range使用该xrange函数。不同之处(在 python 2.x 中)是range构建一个值列表(因此它必须为其分配内存空间),同时xrange构建一个范围为给定值的可迭代对象。

于 2012-05-12T17:02:25.967 回答
1

你似乎是完全正确的,你的比较是公平的。当您只比较开销时,支持迭代器协议的类将比生成器函数慢。

然而,在现实世界中,如果代码复杂到足以证明一个类的合理性,那么算法的运行时间将使开销相形见绌,因此它与程序的运行时间完全无关。

您在这里担心微优化。你不应该。专注于编写好的、可读的代码,并为工作使用正确的算法。类版本中属性查找和方法调用所花费的时间不会成为您的瓶颈。

于 2012-05-12T17:11:14.877 回答