自定义迭代器性能

时间:2012-01-20 18:52:08

标签: python iterator

当使用自定义迭代器迭代一个小容器时,我遇到了看起来非常令人惊讶的性能差异。我希望有人能够帮助我理解这些差异的来源。

首先是一些背景;我正在使用boost :: python编写一些python扩展模块,其中一个包含绑定到实现getitem的3d float向量类型。因为它有getitem它可以迭代它,但它似乎很慢,但它不明显为什么所以我决定在python中使用一些简单的自定义迭代器来更好地了解事情是如何工作的。这些迭代器来自哪里......

class MyIterator1(object):
    __slots__ = ['values', 'popfn']

    def __init__(self):
        self.values = ['x', 'y', 'z']
        self.popfn = self.values.pop

    def __length_hint__(self):
        return 3

    def __iter__(self):
        return self

    def next(self):
        try:
            return self.popfn()
        except IndexError:
            raise StopIteration

class MyIterator2(object):
    __slots__ = ['values', 'itfn']

    def __init__(self):
        self.values = ['x', 'y', 'z']
        it = iter(self.values)
        self.itfn = it.next

    def __length_hint__(self):
        return 3

    def __iter__(self):
        return self

    def next(self):
        return self.itfn()

class MyIterator3(object):
    __slots__ = ['values', 'i']

    def __init__(self):
        self.values = ['x', 'y', 'z']
        self.i = 0

    def __length_hint__(self):
        return 3

    def __iter__(self):
        return self

    def next(self):
        if self.i >= 3:
            raise StopIteration
        value = self.values[self.i]
        self.i += 1
        return value

def MyIterator4():
    val = ['x', 'y', 'z']
    yield val[0]
    yield val[1]
    yield val[2]

接下来,我通过带有timeit模块的脚本运行这些模块(假设上面的代码位于名为testiter的模块中)

import timeit

timer1 = timeit.Timer('r = list(testiter.MyIterator1())', 'import testiter')
timer2 = timeit.Timer('r = list(testiter.MyIterator2())', 'import testiter')
timer3 = timeit.Timer('r = list(testiter.MyIterator3())', 'import testiter')
timer4 = timeit.Timer('r = list(testiter.MyIterator4())', 'import testiter')
timer5 = timeit.Timer('r = list(iter(["x", "y", "z"]))', 'import testiter')

print 'list(testiter.MyIterator1())'
print timer1.timeit()

print "\n"

print 'list(testiter.MyIterator2())'
print timer2.timeit()

print "\n"

print 'list(testiter.MyIterator3())'
print timer3.timeit()

print "\n"

print 'list(testiter.MyIterator4())'
print timer4.timeit()

print "\n"

print 'list(iter(["x", "y", "z"]))'
print timer5.timeit()

这打印出以下

list(testiter.MyIterator1())
8.57359290123


list(testiter.MyIterator2())
5.28959393501


list(testiter.MyIterator3())
6.11230111122


list(testiter.MyIterator4())
2.31263613701


list(iter(["x", "y", "z"]))
1.26243281364

不出所料,python listiterator是最快的,相当大的余地。我认为这取决于python中的一些神奇优化。生成器也比MyIterator类快得多,我再也不会感到非常惊讶,并且假设是由于所有的工作都在c中完成,但这只是猜测。现在其他人更容易混淆/侮辱。 try / except语句在这种情况下看起来是否像它们看起来那么昂贵,或者还有其他什么东西在继续?

任何帮助解释这些差异的人都将不胜感激!为长篇大论道歉。

1 个答案:

答案 0 :(得分:2)

我的头脑中有一些想法;抱歉,如果他们不够明显:

  • 第一个迭代器使用列表的pop来实现next,这意味着在检索每个元素后列表会发生变异。也许这种动态分配的复合数据结构的变异正在降低性能。所有这些都取决于列表的实现细节,因此这可能完全无关紧要。
  • 最后一个迭代器使用在简单上下文中连接到语言(yield)的功能来生成结果。在猜测中,我会说这表明优化的空间比试图获得相同结果的自定义迭代器类更多。
  • 第5个计时器不使用自定义迭代器,而是直接使用为列表提供的迭代器。列表的迭代器可能已经过优化,并且没有这些自定义类使用的间接层,其中一些在内部使用这样的列表迭代器。