2010-04-24 7 views
10

Tengo una función foo (i) que toma un número entero y tarda una cantidad significativa de tiempo en ejecutarse. ¿Habrá una diferencia de rendimiento significativa entre cualquiera de las siguientes formas de inicializar un : (. No me importa si la salida es una lista o una matriz numpy)Comprensión de lista, mapa y rendimiento de numpy.vectorize

a = [foo(i) for i in xrange(100)] 

a = map(foo, range(100)) 

vfoo = numpy.vectorize(foo) 
a = vfoo(range(100)) 

¿Hay ¿una mejor manera?

+13

por qué no tratar de temporización que? – mpen

Respuesta

9

el primer comentario que tengo es que debe usar xrange() o range() en todos sus ejemplos. si los mezclas, entonces estás comparando manzanas y naranjas.

i segundos @ noción de Gabe que si usted tiene una gran cantidad de estructuras de datos y son grandes, entonces debe ganar numpy general ... sólo tener en cuenta la mayor parte del tiempo C es más rápido que Python, pero por otra parte, la mayor parte del tiempo, PyPy es más rápido que CPython. :-)

la medida de lo listcomps vs map() llamadas van ... uno hace 101 llamadas a funciones mientras el otro hace 102 no verá una diferencia significativa en el tiempo, como se muestra a continuación utilizando el timeit módulo como @ Marcos ha sugerido:

  • lista por comprensión

    $ python -m timeit "def foo(x):pass; [foo(i) for i in range(100)]"
    1000000 loops, best of 3: 0.216 usec per loop
    $ python -m timeit "def foo(x):pass; [foo(i) for i in range(100)]"
    1000000 loops, best of 3: 0.21 usec per loop
    $ python -m timeit "def foo(x):pass; [foo(i) for i in range(100)]"
    1000000 loops, best of 3: 0.212 usec per loop

  • map() llamada a la función

    $ python -m timeit "def foo(x):pass; map(foo, range(100))"
    1000000 loops, best of 3: 0.216 usec per loop
    $ python -m timeit "def foo(x):pass; map(foo, range(100))"

    $ python -m timeit "def foo(x):pass; map(foo, range(100))"
    1000000 loops, best of 3: 0.215 usec per loop

Con todo esto dicho, sin embargo, yo también decir esto: a menos que usted está planeando en utilizando las listas que se crean a partir de cualquiera de estas técnicas, evitaría por completo. en otras palabras, si todo lo que va a hacer es iterar sobre estos datos, entonces no vale la pena consumir más memoria para crear una lista potencialmente masiva en la memoria cuando solo le interesa ver cada resultado de uno en uno y descartar la lista lo antes posible. ya que lo has sobrepasado

en tales casos, recomiendo encarecidamente el uso de expresiones de generador en su lugar. genexps no crea la lista completa en la memoria ...es una forma más iterativa y perezosa de recordar los elementos. la mejor parte es que su sintaxis es casi idéntica a la de listcomps:

a = (foo(i) for i in range(100)) 

lo largo de las líneas de más de iteración, cambie todas las llamadas a range()xrange() para el resto de los lanzamientos 2.x. luego cambiar de nuevo a range() al portar a Python 3 como xrange() reemplaza y se renombra a range(). :-)

+0

Tenga en cuenta que el mapa requiere que se defina una función, pero las listas no lo comprenden, lo que puede ser un beneficio. Un uso realista de la lista que hace lo mismo que su código. '.' python -m timeit "def foo (x): pase; [Ninguno para i en el rango (100)] "' arroja resultados en mi máquina que demoran alrededor de 2/3 del tiempo de uso de la lista de comprensión. ¿Es esto realmente lo que OP quiere? No sé si lo es, pero lo hace Demuestre que estas preguntas están matizadas y que las conclusiones pueden reflejar mucho más sobre cómo diseñamos nuestro ejemplo que sobre un uso real. –

+0

no del todo. 'map()' tampoco requiere una función, como en 'map (None , rango (100)) '. Tengo un discurso más largo sobre performance y listcomps vs.' map() 'pero el OP no hizo esa pregunta, así que no puedo responderla aquí. Lo que puedo decir es que * realmente * acelera listcomps, tiene que reducir esa función a una expresión y usar * that * (en lugar de la función). Hacer llamadas a funciones tiene una penalización de rendimiento que se magnifica en un ciclo cerrado. – wescpy

+0

* tenga en cuenta que C siempre es más rápido que Python * Tenga en cuenta que esto no es realmente cierto. –

7

Si la función en sí requiere una cantidad significativa de tiempo para ejecutarse, es irrelevante cómo se correlaciona su salida a una matriz. Sin embargo, una vez que comienzas a obtener matrices de millones de números, numpy puede ahorrarte una gran cantidad de memoria.

+0

estuvieron de acuerdo ... un gran número de estructuras de datos son más rápido cuando se procesa en C vs Python puro – wescpy

+2

Tenga en cuenta que el uso de' numpy.vectorize' no hace realmente efectiva mover cosas a C como usar operaciones numéricas reales. –

3

La comprensión de la lista es la más rápida, luego el mapa, luego el numpy en mi máquina. El código numpy es bastante más lento en realidad que los otros dos, pero la diferencia es mucho menor si usa numpy.arange en lugar de range (o xrange) como lo hice en los tiempos que figuran a continuación. Además, si usas psyco, la comprensión de la lista se acelera mientras que las otras dos se ralentizaron para mí. También usé matrices de números más grandes que en tu código y mi función foo acaba de calcular la raíz cuadrada. Aquí hay algunos momentos típicos.

Sin psyco:

list comprehension: 47.5581952455 ms 
map: 51.9082732582 ms 
numpy.vectorize: 57.9601876775 ms 

Con psyco:

list comprehension: 30.4318844993 ms 
map: 96.4504427239 ms 
numpy.vectorize: 99.5858691538 ms 

que utilizan Python 2.6.4 y el módulo timeit.

En base a estos resultados, diría que probablemente no marque la diferencia que elija para la inicialización. Probablemente elegiría el numpy o la comprensión de la lista en función de la velocidad, pero al final debería dejar que lo que está haciendo con la matriz guíe su elección.

+0

Hmm, lo siento, esto es tarde. He estado intentando publicar desde hace un tiempo y obtengo errores. –

18
  • ¿Por qué estás optimizando esto? ¿Ha escrito un código de trabajo probado, luego examinó su algoritmo profiled y descubrió que optimizarlo tendrá un efecto? ¿Estás haciendo esto en un profundo bucle interno donde descubres que estás gastando tu tiempo? Si no, no te molestes.

  • Solo sabrá cuál funciona más rápido para usted al cronometrarlo. Para cronometrarlo de manera útil, deberá especializarlo en su caso de uso real. Por ejemplo, puede obtener diferencias de rendimiento notables entre una llamada a función en una lista de comprensión frente a una expresión en línea; no está claro si realmente deseaba lo primero o si lo redujo a eso para que sus casos sean similares.

  • Usted dice que no importa si usted termina con una matriz o una numpy list, pero si usted está haciendo este tipo de micro-optimización que lo hace materia, ya que los llevará a cabo de manera diferente cuando se utilízalos después. Poner el dedo en eso podría ser complicado, así que con suerte todo el problema es discutible como prematuro.

  • Por lo general, es mejor simplemente usar la herramienta adecuada para el trabajo para mayor claridad, legibilidad, etc. Es raro que me resulte difícil decidir entre estas cosas.

    • Si necesito matrices numpy, las usaría. Yo los usaría para almacenar grandes matrices homogéneas o datos multidimensionales. Los uso mucho, pero rara vez creo que me gustaría usar una lista.
      • Si yo estaba usando estos, me gustaría hacer mi mejor esfuerzo para escribir mis funciones ya vectorizado por lo que no tenía que usar numpy.vectorize. Por ejemplo, times_five a continuación se puede utilizar en una matriz numpy sin decoración.
    • Si no tenía motivos para utilizar numpy, es decir, si yo no estaba resolviendo problemas matemáticos numéricos o el uso de características especiales numpy o almacenamiento de matrices multidimensionales o lo que sea ...
      • Si Tenía una función ya existente, usaría map. Para eso es para eso.
      • Si tuviera una operación que se ajustara a una expresión pequeña y no necesitara una función, usaría una lista de comprensión.
      • Si solo quisiera hacer la operación para todos los casos pero en realidad no necesito almacenar el resultado, usaría un ciclo simple.
      • En muchos casos, utilizaría realmente map y enumeraré los equivalentes perezosos de las comprensiones: itertools.imap y expresiones de generador. Estos pueden reducir el uso de memoria por un factor de n en algunos casos y pueden evitar realizar operaciones innecesarias a veces.

Si lo hace llegar aquí es donde se encuentran los problemas de rendimiento, recibiendo este tipo de cosas es complicado derecha. Es muy común que las personas el tiempo de la caja de juguetes equivocada para sus problemas reales. Peor aún, es extremadamente común que las personas hagan reglas generales tontas basadas en eso.

considerar los siguientes casos (timeme.py se publican a continuación)

python -m timeit "from timeme import x, times_five; from numpy import vectorize" "vectorize(times_five)(x)" 
1000 loops, best of 3: 924 usec per loop 

python -m timeit "from timeme import x, times_five" "[times_five(item) for item in x]" 
1000 loops, best of 3: 510 usec per loop 

python -m timeit "from timeme import x, times_five" "map(times_five, x)" 
1000 loops, best of 3: 484 usec per loop 

Un obsever ingenuo concluiría que el mapa es el de mejor desempeño de estas opciones, pero la respuesta sigue siendo "depende". Considere el poder de usar los beneficios de las herramientas que está utilizando: las listas de comprensión le permiten evitar la definición de funciones simples; Numpy te permite vectorizar cosas en C si estás haciendo las cosas bien.

python -m timeit "from timeme import x, times_five" "[item + item + item + item + item for item in x]" 
1000 loops, best of 3: 285 usec per loop 

python -m timeit "import numpy; x = numpy.arange(1000)" "x + x + x + x + x" 
10000 loops, best of 3: 39.5 usec per loop 

Pero eso no es todo, hay más. Considere el poder de un cambio de algoritmo. Puede ser aún más dramático.

python -m timeit "from timeme import x, times_five" "[5 * item for item in x]" 
10000 loops, best of 3: 147 usec per loop 

python -m timeit "import numpy; x = numpy.arange(1000)" "5 * x" 
100000 loops, best of 3: 16.6 usec per loop 

A veces, un cambio de algoritmo puede ser aún más eficaz. Esto será más y más efectivo a medida que los números se hacen más grandes.

python -m timeit "from timeme import square, x" "map(square, x)" 
10 loops, best of 3: 41.8 msec per loop 

python -m timeit "from timeme import good_square, x" "map(good_square, x)" 
1000 loops, best of 3: 370 usec per loop 

Y aún ahora, todo esto puede tener poca relación con su problema real. Parece que numpy es tan genial si puedes usarlo correctamente, pero tiene sus limitaciones: ninguno de estos ejemplos numpy usa objetos reales de Python en las matrices. Eso complica lo que debe hacerse; mucho, incluso ¿Y qué pasa si usamos C tipo de datos? Estos son menos robustos que los objetos de Python. Ellos no son Nullable. Los enteros se desbordan. Tienes que hacer un trabajo extra para recuperarlos. Están tipicamente tipificados. Algunas veces estas cosas prueban ser problemas, incluso inesperados.

Así que ahí tienes: una respuesta definitiva. "Depende."


# timeme.py 

x = xrange(1000) 

def times_five(a): 
    return a + a + a + a + a 

def square(a): 
    if a == 0: 
     return 0 

    value = a 
    for i in xrange(a - 1): 
     value += a 
    return value 

def good_square(a): 
    return a ** 2 
Cuestiones relacionadas