Python 在iterable类上循环的开销

Python 在iterable类上循环的开销,python,performance,benchmarking,Python,Performance,Benchmarking,我在摆弄Python的生成器和iterable类,只是为了好玩。基本上,我想测试一些我从来都不太确定的东西:python中的类有一些显著的开销,如果可以的话,最好依赖于实现yield的方法,而不是实现迭代器协议的类 我在谷歌上找不到关于这个主题的令人满意的解释,所以我决定自己用这两个简单的脚本来测试它们:func_iter.py和class_iter.py 下面是func\u iter.py: #!/usr/bin/env python import time x = 0 def cre

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

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

下面是
func\u 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)
#!/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)
下面是
class\u 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)
#!/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\u iter.py
):

以下是每种设备的平均运行时间:

class_iter.py: 0.0864
func_iter.py: 0.0307
现在,我的问题是:

  • 我的方法正确吗?我的比较公平吗
  • 如果是这样,为什么会有这么大的区别?为什么
    class_iter.py
    运行的时间几乎是
    func_iter.py
    的三倍
  • 如果没有,我如何改进我的方法或提出更好的比较
    编辑:根据Dacav的建议,我还尝试使用
    xrange
    而不是
    range
    运行
    func\u iter.py
    。这将使其平均运行时间减少到0.0263秒。

    如果您使用python,很有可能您不以软件性能为目标,但您更关心开发的快速性和敏捷性

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


    例如,基于
    yield
    的版本的一个可能改进是删除
    range
    功能,并改用
    xrange
    功能。不同之处(在Python2.x中)在于
    range
    构建了一个值列表(因此它必须在内存中为它分配空间),而
    xrange
    构建了一个基于给定值的iterable对象。

    类版本花费大量时间访问自己的变量。每个
    self。无论什么
    都会消耗周期。如果将
    \uuuu iter\uuuu
    定义为生成器,并尽量减少实例变量的使用,则类和函数版本之间的差异可以忽略不计:

    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
    
    因此,第二个生成器类几乎与函数版本一样快


    请注意,示例中的
    Generator
    Generator2
    并不完全等效,有时您不能简单地用生成器替换“普通”迭代器(例如封送)。

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

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


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

    谢谢!我刚刚试过这个,现在
    func_iter.py
    的平均时间减少到了0.0263。我认为这不是他想要测试的。这里比较的是一个生成器和一个生成器,而不是一个生成器和迭代器协议。是的,该类仍然是可编辑的,但是(例如)您不能pickle它的状态,因为该状态是一个不是该类成员的生成器。已确认!它可能还要慢0.002秒~可以安全地假设这种差异是由于类实例化所需的时间造成的吗?@bow:是的,类实例化+访问
    \uuuuu iter\uuu
    中的实例变量。如果你想知道幕后到底发生了什么,试试
    dis
    模块。开销也(主要)来自函数调用。对于代码中不使用生成器的每次迭代,Python都必须调用next()方法,该方法必须创建函数的框架和局部范围。这需要时间。生成器也有一个框架和局部作用域,但它们只创建一次,并在每次迭代中重复使用。@mdob:是的,这是正确的。Generator2是一个Iterable,其
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。我只是对一些我很久以前就想过的东西感到好奇(但从未真正证明过)~我相信你知道打破神话很有趣:D.@bow我想说你问错了问题。在合理范围内,速度差是多少并不重要。重要的是选择使代码更好的方法。你是对的,一个比较慢,但你应该考虑一下这一点是错的。@bow还值得注意的是,这是一个解决实际问题的网站,而不是理论问题的网站(见FAQ),因此你肯定会得到至少一些解决问题的答案,就好像它不仅仅是学术问题一样。