自定义迭代器性能
我在使用自定义迭代器遍历一个小容器时,发现性能差异挺大的,这让我感到有些意外。我希望有人能帮我理解这些差异是怎么来的。
先说说背景;我正在用boost::python写一些Python扩展模块,其中一个模块绑定了一个3D浮点向量类型,这个类型实现了getitem。因为它有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 个回答
2
我想到了一些点,可能不太容易理解,抱歉:
- 第一个迭代器是用列表的
pop
方法来实现next
的,这样每次取出一个元素后,列表都会被改变。也许这种动态分配的复合数据结构的变化会影响性能。不过这要看列表的具体实现,所以可能并不相关。 - 最后一个迭代器使用了语言内置的一个特性(yield),在简单的情况下生成结果。我的猜测是,这说明它比一个自定义的迭代器类有更多的优化空间,因为后者试图实现相同的结果。
- 第五个计时器没有使用自定义迭代器,而是直接使用了列表提供的迭代器。列表的迭代器可能已经经过很好的优化,而且这些自定义类使用的某些迭代器内部也可能在使用这种列表迭代器,所以没有额外的间接层。