¿Ventajas de rendimiento para los iteradores?

Las ventajas de rendimiento (si las hay) se ofrecen mediante el uso de iteradores. Parece ser el ‘Camino correcto’ para resolver muchos problemas, pero ¿crea un código más rápido / más consciente de la memoria? Estoy pensando específicamente en Python, pero no restrinja las respuestas a eso.

En realidad, hay un muy buen correo en la lista de correo de python sobre esto: Iterators vs Lists . Es un poco anticuado (de 2003), pero por lo que sé, sigue siendo válido.

Aquí está el resumen:

Para pequeños conjuntos de datos, los enfoques basados ​​en iteradores y listas tienen un rendimiento similar. Para conjuntos de datos más grandes, los iteradores ahorran tiempo y espacio.

Lo que obtendría de esto es esto: los iteradores deben ser preferidos a cargar datos en una lista si es posible. Pero a menos que tenga un gran conjunto de datos, no contorsione su código para hacer algo que debería encajar en una lista para trabajar con un iterador.

Para Python, los generadores serán más rápidos y tendrán una mejor eficiencia de memoria. Solo piense en un ejemplo de range(1000) vs xrange(1000) (Esto ha sido cambiado en 3.0, el rango ahora es un generador). Con Rango, usted precomstack su lista, pero XRange solo tiene un objeto generador y produce el siguiente elemento cuando es necesario.

La diferencia de rendimiento no es grande en cosas pequeñas, pero tan pronto como comience a utilizarlas cada vez más y más grandes conjuntos de información lo notará rápidamente. Además, no solo tiene que generar y luego pasar, sino que también consumirá memoria adicional para su elemento preconstruido en el que, al igual que con la expresión del generador, solo se crea un elemento a la vez.

El principal beneficio de los iteradores no es el rendimiento. En mi experiencia, la solución más eficaz es crear un algoritmo que incruste la estructura de datos que elija. El beneficio de los iteradores es que le permiten desacoplar los datos y el algoritmo y, por lo tanto, generalizar y reutilizar ambos. Si esto también se puede hacer sin (o con poca) degradación del rendimiento, es una ganancia neta.

Mi ejemplo favorito de uso de iterador se puede encontrar en la Biblioteca de plantillas estándar de C ++. Se las arregla para demostrar el poder y la belleza de la abstracción al separar limpiamente el contenedor y el algoritmo sin sacrificar el rendimiento. La comprensión de este diseño tuvo un profundo efecto en la forma en que pienso sobre el código.

Para respaldar la respuesta de @Christian Witts :

range vs. rendimiento de range

 python25 -mtimeit "for i in xrange(1000): pass" 10000 loops, best of 3: 56.3 usec per loop python25 -mtimeit "for i in range(1000): pass" 10000 loops, best of 3: 80.9 usec per loop python26 -mtimeit "for i in xrange(1000): pass" 10000 loops, best of 3: 48.8 usec per loop python26 -mtimeit "for i in range(1000): pass" 10000 loops, best of 3: 68.6 usec per loop 

por cierto, ni range() ni xrange() son iteradores:

 >>> hasattr(range(1), 'next') False >>> hasattr(xrange(1), 'next') False >>> iter(xrange(1))  >>> iter(range(1))  >>> iter([])  >>> iter(i for i in (1,))  >>> (i for i in (1,))  

Los iteradores son solo clases que implementan una interfaz particular , específicamente una interfaz para ir a la siguiente . En Python, las listas, las tuplas, los dados, las cadenas y los archivos implementan esta interfaz. Si se implementan de forma deficiente, puede resultar en un rendimiento deficiente, pero no hay nada inherente a la interfaz que implique un rendimiento bueno o malo.

Mi inferencia de muchas de las respuestas anteriores es “Use la lista para codificar. Si es necesario, vuelva a factorizar los iteradores” La diferencia no es aparente a menos que tenga un conjunto de datos grande.

Otra cosa a tener en cuenta es que, incluso cuando se usan listas a menudo, el conjunto de datos con el que estamos operando es cada vez más pequeño y más pequeño.

Un iterador es simplemente un objeto que proporciona métodos para permitir el desplazamiento a través de una colección. Podría atravesar todos los elementos de una matriz o todos los nodos de un árbol con la misma interfaz. Los árboles y las matrices son estructuras de datos muy diferentes y requieren diferentes métodos para atravesar … pero con un iterador puedes recorrer todos los elementos de la misma manera.

Para un tipo de colección, también puede haber diferentes formas de recorrerla y una sola colección podría tener múltiples iteradores. Podría tener un primer iterador de profundidad o un primer iterador de ancho que atraviese una estructura de árbol y devuelva los nodos en diferentes órdenes. . Los iteradores no están diseñados para el rendimiento … sino que suelen proporcionar una interfaz consistente para atravesar estructuras.

Hay una respuesta que creo que confunde un poco el concepto de generador e iterador. Así que decidí intentar responder esta pregunta con un ejemplo de metáfora.

Estoy trabajando en una cocina, mi jefe me encomienda la tarea de sumr el peso de 10 (o 100 o un millón) de panes. Tengo una balanza y una calculadora (trucos de magia de mi algoritmo). A continuación se muestran el objeto iterable, generador, iterador, diferencia de enfoque:

  1. Objeto iterable: Cada pan se almacena en una caja (memoria), peso el primer (o el 0º) pan, pongo su peso, y devuelvo el pan a la caja, luego voy a la siguiente, lo peso y pongo de vuelta, una y otra vez, etc., etc. Al final, obtuve el peso total, y los 10 (100 o millones) de pan todavía están allí en sus cajas.

  2. Generador: no hay suficientes cajas para almacenar todo este pan, así que pedí la ayuda de un panadero (el generador), él hace el primer pan, me lo da, lo pesé, lo coloqué, lo arrojé. de distancia y pregúntele por otro, una y otra vez, etc., hasta que consiga el último pan (o tal vez el panadero se quede sin harina). Al final, tengo el resultado, ninguno de los panes está ahí. Pero a quién le importa, mi jefe solo me pide que pese estos panes, él no dijo que no puedo tirarlos (qué shiny ayudante de camarero).

  3. Iterador: Le pido a alguien (iterador) que me ayude a mover el primer pan a la báscula, lo pesé y puse el resultado. Este alguien iría a buscar el siguiente para medir, una y otra vez, etc. Realmente no tengo idea si alguien (iterador) obtiene el pan de una caja o de un panadero. Con el tiempo, tengo el peso total, no me importa.

De todos modos, para resumir:

  1. El objeto que se puede iterar necesita algo de memoria para almacenar los datos para comenzar. Al final, los datos todavía están allí.

  2. El generador no necesita memoria para almacenar datos, para empezar, genera datos sobre la marcha.

  3. Iterador es un canal entre el algoritmo y sus datos. Es posible que estos datos ya hayan estado allí y se hayan almacenado en la memoria o que se generen sobre la marcha mediante un generador. En el primer caso, esa memoria se liberaría poco a poco a medida que el iterador se mantiene iterando. Así que estoy de acuerdo con la respuesta anterior de que el iterador es bueno debido a su abstracción que permite el aislamiento del algoritmo y los datos.

Python no funciona exactamente así. Espero que ayude a aclarar un poco.

Un poco fuera de tema, pero agrega más peso al uso de las listas sobre los iteradores en general: con los iteradores es más fácil tener efectos secundarios, considere esto:

 def foo(arg: Iterable[str]): print(list(arg)) # side effect: arg is exhausted at this point ... 

Se puede decir que las pruebas deben captar esto, pero a veces no es así. Las listas no tienen este problema porque son apátridas (en el sentido de iteración).