2010-08-15 19 views
12

Actualmente estoy en un proyecto de aprendizaje personal donde leo en una base de datos XML. Me encuentro escribiendo funciones que recopilan datos y no estoy seguro de cuál sería una forma rápida de devolverlos.¿Cuál es generalmente más rápido, un rendimiento o un apéndice?

que es generalmente más rápido:

  1. yield s, o
  2. varios append() s dentro de la función a continuación return la consiguiente list?

estaría feliz de saber en qué situaciones donde yield s sería más rápido que append() s o viceversa.

Respuesta

15

yield tiene la enorme ventaja de ser perezoso y la velocidad no es generalmente la razón mejor para usarlo. Pero si funciona en su contexto, entonces no hay razón para no usarlo:

# yield_vs_append.py 
data = range(1000) 

def yielding(): 
    def yielder(): 
     for d in data: 
      yield d 
    return list(yielder()) 

def appending(): 
    lst = [] 
    for d in data: 
     lst.append(d) 
    return lst 

Este es el resultado:

python2.7 -m timeit -s "from yield_vs_append import yielding,appending" "yielding()" 
10000 loops, best of 3: 80.1 usec per loop 

python2.7 -m timeit -s "from yield_vs_append import yielding,appending" "appending()" 
10000 loops, best of 3: 130 usec per loop 

Al menos en esta prueba muy sencilla, yield es más rápido que append .

+1

¿_lazy_ significa _bajo requisito de memoria_? – Kit

+2

Escribí un compresor/descompresor para el algoritmo WKdm. Después de perfilar una función que descomprime los bits en una lista fue la más lenta. Lo convertí en un generador y fue aún más lento. La versión de rendimiento proporcionó aproximadamente 22 MB/s, y la versión adjunta proporcionó aproximadamente 38 MB/s. Entonces, realmente depende de lo que estés haciendo. – Christopher

+2

la búsqueda 'lst.append' puede ralentizar' anexar() '. Puede intentarlo con 'append = lst.append' fuera del ciclo. – jfs

7

Hace poco me hice una pregunta similar explorando formas de generar todas las permutaciones de una lista (o tupla) añadiéndolas a una lista oa través de un generador, y encontradas (para permutaciones de longitud 9, que toman alrededor de un segundo o así para generar):

  • el enfoque ingenuo (permutaciones están listas, añadir a la lista, la lista de retorno de listas) toma alrededor de tres veces el tiempo de itertools.permutations
  • uso de un generador (es decir yield) reduce este por aprox . 20%
  • El uso de un generador y la generación de tuplas es el más rápido, el doble de tiempo que el itertools.permutations.

Tome con un grano de sal! La sincronización y la creación de perfiles fue muy útil:

if __name__ == '__main__': 
    import cProfile 
    cProfile.run("main()") 
6

Existe una alternativa incluso más rápida a la producción de TH4Ck(). Es lista de comprensión.

In [245]: def list_comp(): 
    .....:  return [d for d in data] 
    .....: 

In [246]: timeit yielding() 
10000 loops, best of 3: 89 us per loop 

In [247]: timeit list_comp() 
10000 loops, best of 3: 63.4 us per loop 

Por supuesto, es bastante tonto para micro-benchmark estas operaciones sin conocer la estructura de su código. Cada uno de ellos es útil en situaciones de diferencia. Por ejemplo, la comprensión de listas es útil si desea aplicar una operación simple que puede expresarse como una sola expresión. El rendimiento tiene una ventaja significativa para que usted pueda aislar el código transversal en un método generador. Cuál es apropiado depende mucho del uso.

+0

En realidad, quería incluir listas de comprensión, pero estoy eligiendo entre estas dos: '[n para n en func_that_yields()]' o '[n para n en func_that_returns_an_iterable()]'. Tenga en cuenta que 'n' puede ser un simple elemento desempaquetado, o una compleja operación elemento por elemento. De todos modos, buen punto tienes ahí :) – Kit

0

Primordialmente debe decidir, si necesita generador, esto también tiene un método mejorado. Como generador de listas "[elem for elem in somethink]". Y los generadores se recomiendan si solo usa valor en la lista para algunas operaciones. Pero si necesita una lista para muchos cambios y trabaja con muchos elementos al mismo tiempo, esta debe ser una lista. (Al igual que el 70% de las veces si la lista de uso del programador es estándar, mejor será el generador.usa menos memoria, solo mucha gente simplemente no ve otra forma de lista. Desafortunadamente en nuestra época, muchas personas hacen pis en la buena optimización, y simplemente lo hacen para trabajar).

Si usa el generador para la lista para mejorar el rendimiento, hagamos lo mismo con los tipos de rendimiento. De todos modos, tenemos varios métodos más optimizados para todas las acciones en el lenguaje de programación Python.

El rendimiento es más rápido que el rendimiento, y lo demostraré. Sólo tienes que comprobar estos chicos:

data = range(1000) 

def yielder(): 
    yield from data 

def appending(): 
    L = [] 
    app = list.append 
    for i in data: 
     app(L, i) 
    return L 

def list_gen(): 
    return [i for i in data] 

Por supuesto anexar será más lento que otras ideas, lástima pues creamos y lista de extender cualquier momento bucle. Solo el ciclo "para" está muy desoptimizado, si puedes evitarlo, hazlo. Porque en cualquier paso esta función carga el siguiente elemento y escribe nuestra variable, para obtener este valor de objeto en la memoria. Así que saltamos a cualquier elemento, creamos una referencia, ampliamos la lista en bucle (el método declarado es el optymalizer de gran velocidad), cuando generamos solo el retorno, el resumen tiene 2000 elementos en dos listas.

list_gen es menos memoria, simplemente devolvemos elementos, pero al igual que arriba, generamos una segunda lista. Ahora tenemos dos listas, datos originales y su copia. Resumen 2000 elementos. Simplemente evitamos el paso con crear referencia a la variable. Porque nuestro gen en las listas evita este paso. Solo escribe elementos.

use la menor memoria posible, ya que solo obtuvimos valor de los datos. Evitamos una referencia. Por ejemplo:

data = range(1000) 

def yielder(): 
    yield from data 

def list_gen(): 
    return [i for i in data] 

#Now we generate next reference after line [i for i in data] 
for i in list_gen(): 
    #some instruction 

#This is our first reference, becouse was yield from data. 
for i in yielder(): 
    #some instruction 

Usar sólo un elemento de una cierta instrucción, no todos de la lista, al lado de un valor productora volverá al siguiente bucle, no todas revista 1000 elementos para escribir en referencia.

Srry por poco tema de excavación, justo cuando accidentalmente vino una cruz de búsqueda de google, otros principiantes programadores de pitón pueden ver esta tontería.

Cuestiones relacionadas