2012-05-12 21 views
7

Estaba jugueteando con los generadores de Python y la clase iterable, solo por diversión. Básicamente, quería probar algo de lo que nunca estuve tan seguro: que las clases en Pythons tienen una sobrecarga significativa y es mejor confiar en los métodos que implementan yield en lugar de las clases que implementan un protocolo de iterador, si es posible.python - Sobrecarga en el bucle en una clase iterable

no pude encontrar una explicación satisfactoria sobre este tema en Google, por lo que decidí ponerlas a prueba en mi propio uso de estos dos guiones simples: func_iter.py y class_iter.py

Aquí está func_iter.py:

#!/usr/bin/env python 

import time 

x = 0 
def create_generator(num): 
    mylist = range(num) 
    for i in mylist: 
     yield i 

t = time.time() 
gen = create_generator(100000) 

for i in gen: 
    x = x + i 

print "%.3f" % (time.time() - t) 

Y aquí es class_iter.py:

#!/usr/bin/env python 

import time 

x = 0 

class Generator(object): 

    def __init__(self, num): 
     self.start = 0 
     self.end = num 

    def __iter__(self): 
     return self 

    def next(self): 
     if self.start == self.end: 
      raise StopIteration 
     else: 
      self.start = self.start + 1 
      return self.start 

t = time.time() 
gen = Generator(100000) 

for i in gen: 
    x = x + i 

print "%.3f" % (time.time() - t) 

entonces me encontré con cada uno de ellos 10 veces usando Thi s en bash (por class_iter.py, por ejemplo):

for i in {1..10}; do ./class_iter.py; done 

Y aquí están los tiempos medios de funcionamiento para cada uno de ellos:

class_iter.py: 0.0864 
func_iter.py: 0.0307 

Ahora, mis preguntas son:

  1. Son mis métodos son correctos? ¿Es justa mi comparación?
  2. Si es así, ¿por qué la gran diferencia? ¿Por qué class_iter.py tomó casi tres veces más tiempo que func_iter.py para ejecutarse?
  3. Si no es así, ¿cómo puedo mejorar mis métodos o encontrar una mejor comparación?

EDIT: Como se sugirió Dacav, también intenté correr func_iter.py usando xrange en lugar de range. Esto disminuye su tiempo de ejecución promedio a 0.0263 segundos.

Respuesta

5

La versión de clase pasa mucho tiempo accediendo a sus propias variables. Cada self.whatever cuesta ciclos.Si define su __iter__ como generador y minimizar el uso de variables de instancia, la diferencia entre las versiones de clase y función será insignificante:

setup = """ 
def create_generator(num): 
    mylist = range(num) 
    for i in mylist: 
     yield i 

class Generator(object): 

    def __init__(self, num): 
     self.start = 0 
     self.end = num 

    def __iter__(self): 
     return self 

    def next(self): 
     if self.start == self.end: 
      raise StopIteration 
     else: 
      self.start = self.start + 1 
      return self.start 

class Generator2(object): 

    def __init__(self, num): 
     self.mylist = range(num) 

    def __iter__(self): 
     for i in self.mylist: 
      yield i 
""" 

import timeit 

print timeit.timeit('for p in create_generator(1000):p', setup, number=1000) 
print timeit.timeit('for p in Generator(1000):p', setup, number=1000) 
print timeit.timeit('for p in Generator2(1000):p', setup, number=1000) 

Resultados:

0.158941984177 
0.696810007095 
0.160784959793 

por lo que la segunda clase del generador es casi tan rápido como la versión de la función.

Tenga en cuenta que Generator y Generator2 en el ejemplo no son totalmente equivalentes, hay casos en los que no se puede reemplazar simplemente un iterador "simple" con un generador (por ejemplo, clasificación).

+0

No creo que eso sea lo que quería probar. Está comparando un generador con un generador aquí, no un generador con el protocolo del iterador. Sí, la clase todavía es iterable, pero (por ejemplo) no puede resumir su estado porque el estado es un generador que no es miembro de la clase. – agf

+0

¡Confirmado! Todavía es más lento por quizás 0.002 segundos ~ ¿es seguro asumir que esta diferencia se debe al tiempo que lleva crear una instancia de la clase? – bow

+0

@bow: sí, creación de instancias de clase + accediendo a la variable de instancia en '__iter__'. Si tiene curiosidad por ver qué ocurre exactamente detrás de escena, intente con el módulo 'dis'. – georg

1

Si está usando python hay buenas posibilidades de que no esté apuntando al rendimiento del software, pero le preocupa más el hecho de ser rápido y ágil en el desarrollo.

Dicho eso, creo que el método de comparación es bastante justo siempre que su código sea lo suficientemente inteligente como para evitar el sesgo de una solución.

Por ejemplo, una posible mejora para la versión basada en yield podría eliminar la función range y utilizar la función xrange en su lugar. La diferencia (en Python 2.x) es que range crea una lista de valores (por lo que debe asignarle espacio en memoria) mientras que xrange construye un objeto iterable que se extiende en los valores dados.

+0

Gracias! Acabo de probar esto, y el tiempo promedio para '' func_iter.py'' ahora disminuye a 0.0263. – bow

1

Parece ser completamente correcto y su comparación es justa. Cuando se compara solo la sobrecarga, la clase que soporta el protocolo del iterador será más lenta que una función del generador.

Sin embargo, en el mundo real, si el código es bastante complicada para justificar una clase, el tiempo de ejecución del algoritmo se empequeñecen la sobrecarga, y por lo que será completamente irrelevante para el tiempo de ejecución de su programa.

Te preocupes por las micro-optimizaciones aquí. No deberias. Concéntrese en escribir un buen código legible y usar el algoritmo correcto para el trabajo. La cantidad de tiempo dedicado a las búsquedas de atributos y métodos en la versión de clase no será su cuello de botella.

+0

Ah :), mi intención no era realmente optimizar un código de producción (aunque esto podría relacionarse un poco). Tenía curiosidad por algo que hacía tiempo que pensaba (pero nunca lo había probado) ~ y estoy seguro de que sabes que los mitos reveladores son divertidos: D. – bow

+0

@bow Estoy tratando de decir que estás haciendo la pregunta incorrecta. No importa cuál sea la diferencia de velocidad, dentro de lo razonable. Lo que importa es elegir el método que mejore su código. Tienes razón en que uno es más lento, pero está mal que deberías pensar en eso en absoluto. – agf

+0

@bow También vale la pena señalar que este es un sitio para problemas reales, no teóricos (consulte las preguntas frecuentes), por lo que es probable que obtenga al menos algunas respuestas que abordan la pregunta como si no fuera solo académica. – agf

Cuestiones relacionadas