2010-03-09 16 views
9

me encuentro con el siguiente pequeño dilema molesto una y otra vez en Python:Python: ¿Qué tan caro es crear una pequeña lista muchas veces?

Opción 1: (?)

más limpio pero más lento si llama muchas veces desde entonces ser re-creado una_lista para cada llamada de hacer_algo()

def do_something():  
    a_list = ["any", "think", "whatever"]  
    # read something from a_list 

Opción 2:

más feo pero más eficiente (piezas de la creación una_lista todo de nuevo)

a_list = ["any", "think", "whatever"]  
def do_something():  
    # read something from a_list 

¿Qué opinas?

+8

en caso de duda, busque el código de autoedición más legible, elegante que pueda hasta que un perfilador de rendimiento le diga que no lo haga. –

+0

Sí, lo sé. Pero esto es algo tan pequeño, molesto y fácil de evitar ... – GabiMe

+4

"pequeño" significa ignorarlo. Haz lo que sea más claro. Deje las consideraciones de rendimiento a un lado hasta que pueda * demostrar * que es un problema. –

Respuesta

16

¿Qué tiene de feo?

¿Los contenidos de la lista son siempre constantes, como en su ejemplo? Si es así: las versiones recientes de Python (desde 2.4) optimizarán eso al evaluar la expresión constante y mantener el resultado, pero solo si es una tupla. Entonces podrías cambiarlo por una tupla.O podrías dejar de preocuparte por pequeñas cosas como esa.

He aquí una lista de constantes y una tupla de constantes:

>>> def afunc(): 
... a = ['foo', 'bar', 'zot'] 
... b = ('oof', 'rab', 'toz') 
... return 
... 
>>> import dis; dis.dis(afunc) 
    2   0 LOAD_CONST    1 ('foo') 
       3 LOAD_CONST    2 ('bar') 
       6 LOAD_CONST    3 ('zot') 
       9 BUILD_LIST    3 
      12 STORE_FAST    0 (a) 

    3   15 LOAD_CONST    7 (('oof', 'rab', 'toz')) 
      18 STORE_FAST    1 (b) 

    4   21 LOAD_CONST    0 (None) 
      24 RETURN_VALUE 
>>> 
+1

¿De verdad? No lo sabía. Las tuplas se optimizan para crearse solo una vez. Interesante .. Esto probablemente resolvería el 90% de los casos del OP – GabiMe

+0

@AnonymousDriveByDownVoter: ¿me gustaría dejar un comentario? –

4

Nunca crees algo más de una vez si no tienes que hacerlo. Esta es una optimización simple que puede hacerse por su parte y personalmente no encuentro el segundo ejemplo feo en absoluto.

Algunos pueden argumentar que no deben preocuparse por la optimización de pequeñas cosas como esta, pero creo que algo tan simple de arreglar debería hacerse de inmediato. No me gustaría que tu aplicación cree copias múltiples de cualquier cosa que no necesite simplemente para preservar un sentido arbitrario de "belleza del código". :)

3

si su a_list no cambia, aléjelo de la función.

0

Si la lista nunca se modifica, ¿por qué usa listas?

Sin conocer sus requisitos reales, recomendaría simplemente usar algunos enunciados if para deshacerse de la lista y la parte "leer algo de la lista" por completo.

4

Opción 3:

def do_something(a_list = ("any", "think", "whatever")): 
    read something from a_list 

Opción 3 en comparación con la Opción 1:

Ambos son igualmente legible en mi opinión (aunque algunos parecen pensar de manera diferente en los comentarios :-)!). Incluso podría escribir la Opción 3 como esta

def do_something(
    a_list = ("any", "think", "whatever")): 
    read something from a_list 

que realmente minimiza la diferencia en términos de legibilidad. A diferencia de la Opción 1, sin embargo, la Opción 3 define a_list solo una vez, en el momento en que se define do_something. Eso es exactamente lo que queremos.

Opción 3 en comparación con la Opción 2:

se deben evitar las variables globales si es posible. La opción 3 te permite hacer eso. Además, con la Opción 2, con el tiempo o si otras personas mantienen este código, la definición de a_list podría separarse de def do_something. Esto puede no ser un gran problema, pero creo que es algo indeseable.

+3

Gneee ... Disculpa, ver una lista como un parámetro predeterminado duele. –

+1

-1 "Evita las variables globales cuando puedas" es un buen consejo, pero "Python forza [d] a buscar primero la variable en los locales() dict" es un codswallop completo. El COMPILADOR sabe si es local o global, y en el caso de la opción 2 emite una instrucción LOAD GLOBAL. El dictus locals() se manifiesta cuando y solo cuando se llama a la función locals() - el COMPILADOR sabe dónde están almacenados todos los locales; no se usa dict para ejecutar el código de función/método normal. –

1

Bueno, parece que todo se reduce a la inicialización de la matriz en la función o no:

import time 
def fun1(): 
     a = ['any', 'think', 'whatever'] 
     sum = 0 
     for i in range(100): 
       sum += i 

def fun2(): 
     sum = 0 
     for i in range(100): 
       sum += i 


def test_fun(fun, times): 
     start = time.time() 
     for i in range(times): 
       fun() 
     end=time.time() 
     print "Function took %s" % (end-start) 

# Test 
print 'warming up' 
test_fun(fun1, 100) 
test_fun(fun2, 100) 

print 'Testing fun1' 
test_fun(fun1, 100000) 
print 'Testing fun2' 
test_fun(fun2, 100000) 

print 'Again' 
print 'Testing fun1' 
test_fun(fun1, 100000) 
print 'Testing fun2' 
test_fun(fun2, 100000) 

y los resultados:

>python test.py 
warming up 
Function took 0.000604152679443 
Function took 0.000600814819336 
Testing fun1 
Function took 0.597407817841 
Testing fun2 
Function took 0.580779075623 
Again 
Testing fun1 
Function took 0.595198154449 
Testing fun2 
Function took 0.580571889877 

Parece que no hay diferencia.

+1

El uso del módulo 'timeit' sería más apropiado ... – ChristopheD

+1

Podría ser útil si llama a do_fun1() en alguna parte. ;-) –

+0

Anidarlo dentro de una función es algo completamente diferente.Pruébelo bajo las condiciones que estamos discutiendo: (1) local (2) global (3) predeterminado arg (4) local pero como una tupla en lugar de una lista. También una gran parte del tiempo se gasta en el ciclo for, que es irrelevante. Bote el bucle y agregue una opción (0) sin lista/tupla para luego informar (1) - (0), (2) - (0) etc. –

0

He trabajado en sistemas automatizados que procesan 100.000,000 de registros al día, donde una mejora del 1% en el rendimiento es enorme.

Aprendí una gran lección trabajando en ese sistema: Más rápido es mejor, pero solo cuando sabes cuando es lo suficientemente rápido.

Una mejora del 1% hubiera sido una gran reducción en el tiempo de procesamiento total, pero no es suficiente para cuando necesitáramos nuestra próxima actualización de hardware. Mi aplicación fue tan rápida, que la cantidad de tiempo que pasé tratando de ordeñar ese último 1% probablemente cueste más de lo que un servidor nuevo podría tener.

En su caso, tendría que llamar a do_algo de manera decenal de miles de veces antes de hacer una diferencia significativa en el rendimiento. En algunos casos eso marcaría la diferencia, en otros no.

2
  1. usted tiene algunos datos
  2. usted tiene un método asociados a ella
  3. Usted no desea mantener los datos a nivel mundial por el simple hecho de optimizar la velocidad del método a menos que sea necesario.

Creo que esto es para lo que son las clases.

class Processor: 
    def __init__(this): 
     this.data = "any thing whatever".split() 
    def fun(this,arg): 
     # do stuff with arg and list 

inst = Processor() 
inst.fun("skippy) 

Además, si algún día desea separar los datos en un archivo, sólo puede modificar el constructor para hacerlo.

Cuestiones relacionadas