Me encuentro con lo que parece ser una sorprendente diferencia de rendimiento al iterar sobre un contenedor pequeño con un iterador personalizado. Esperaba que alguien pudiera ayudarme a entender de dónde vienen estas diferencias.rendimiento del iterador personalizado
Primero algo de contexto; Estoy escribiendo varios módulos de extensión de Python usando boost :: python, uno de los cuales contiene un enlace a un tipo de vector flotante 3d que implementa getitem. Como tiene getitem es posible iterar sobre él, sin embargo, parece bastante lento, pero no es obvio por qué, así que decidí jugar con algunos iteradores personalizados simples en Python para tener una mejor idea de cómo funcionan las cosas. Que es donde estos iteradores vinieron de ...
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]
siguiente me encontré con estos a través de una secuencia de comandos con el módulo timeit (que supone el código anterior se encuentra en un módulo llamado 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()
Esta impresora fuera de la siguiente
list(testiter.MyIterator1())
8.5735929
list(testiter.MyIterator2())
5.28959393501
list(testiter.MyIterator3())
6.11230111122
list(testiter.MyIterator4())
2.31263613701
list(iter(["x", "y", "z"]))
1.26243281364
Como era de esperar el catalogador python es el más rápido, por un buen margen. Supongo que esto se debe a algunas optimizaciones mágicas dentro de Python. El generador también es considerablemente más rápido que las clases de MyIterator, lo que de nuevo no me sorprende enormemente, y supongo que se debe a todo el trabajo que se realiza en c, sin embargo, es solo una suposición. Ahora los otros son más confusos/supresores. ¿Los enunciados try/except son tan caros como parecen en este contexto o está sucediendo algo más?
¡Cualquier ayuda para explicar estas diferencias sería muy apreciada! Disculpas por la larga publicación.
Puede intentar usar tupla en lugar de ['x', 'y', 'z'] (cuando sea posible): la construcción de tuplas es un poco más rápida que la de la lista. –