Я сталкиваюсь с неожиданными различиями в производительности, когда перебираю небольшой контейнер с пользовательским итератором. Я надеялся, что кто-нибудь сможет помочь мне понять, откуда эти различия.
Сначала немного контекста; Я пишу несколько модулей расширения Python, использующих Boost :: 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 является самым быстрым, с большим отрывом. Я предполагаю, что это связано с некоторой магической оптимизацией в Python. Генератор также значительно быстрее, чем классы MyIterator, что опять же меня не сильно удивляет, и я полагаю, это связано со всей работой, выполняемой в c, однако это только предположение. Теперь другие больше сбивают с толку. Являются ли операторы try / исключением такими дорогими, какими они кажутся в этом контексте, или что-то еще происходит?
Любая помощь в объяснении этих различий будет принята с благодарностью! Извиняюсь за длинный пост.