2011-07-02 44 views
18

Estoy desconcertado por esta¿Por qué numpy.array es tan lento?

def main(): 
    for i in xrange(2560000): 
     a = [0.0, 0.0, 0.0] 

main() 

$ time python test.py 

real  0m0.793s 

Veamos ahora con numpy:

import numpy 

def main(): 
    for i in xrange(2560000): 
     a = numpy.array([0.0, 0.0, 0.0]) 

main() 

$ time python test.py 

real 0m39.338s 

ciclos de CPU Santos ayudante personal!

Usando numpy.zeros(3) mejora, pero todavía no es suficiente en mi humilde opinión

$ time python test.py 

real 0m5.610s 
user 0m5.449s 
sys 0m0.070s 

numpy.version.version = '1.5.1'

Si se preguntan si la creación de la lista se omite para la optimización en la primera Por ejemplo, no es:

5   19 LOAD_CONST    2 (0.0) 
      22 LOAD_CONST    2 (0.0) 
      25 LOAD_CONST    2 (0.0) 
      28 BUILD_LIST    3 
      31 STORE_FAST    1 (a) 
+2

Pensamiento rápido: 'numpy.array' es en realidad una estructura de datos más compleja que una lista. Y en el segundo fragmento, crea una lista ** y ** una matriz numpy (en el primero solo una lista). Si esta es la única razón para una gran diferencia, no puedo decirlo. –

+0

@Felix: bien, pero la creación de la lista es rápida, así que incluso si creo una lista y una matriz numpy en el segundo caso, sigue siendo la creación numpy que es el punto caliente aquí, y sin importar cuán compleja pueda ser la estructura sea, todavía es muy costoso ... –

+3

Pero considere: Crear los datos rara vez es el cuello de botella en una aplicación que tan compleja usa numpy.Tampoco sé lo que sucede debajo del capó, pero obviamente hace que los programas matemáticos pesados ​​sean más rápidos al final del día, así que no hay razón para quejarse;) – delnan

Respuesta

34

Numpy está optimizado para grandes cantidades de datos. Dale un pequeño conjunto de 3 longitudes y, como era de esperar, tiene un bajo rendimiento.

Considérese una prueba separada

import timeit 

reps = 100 

pythonTest = timeit.Timer('a = [0.] * 1000000') 
numpyTest = timeit.Timer('a = numpy.zeros(1000000)', setup='import numpy') 
uninitialised = timeit.Timer('a = numpy.empty(1000000)', setup='import numpy') 
# empty simply allocates the memory. Thus the initial contents of the array 
# is random noise 

print 'python list:', pythonTest.timeit(reps), 'seconds' 
print 'numpy array:', numpyTest.timeit(reps), 'seconds' 
print 'uninitialised array:', uninitialised.timeit(reps), 'seconds' 

y la salida es

python list: 1.22042918205 seconds 
numpy array: 1.05412316322 seconds 
uninitialised array: 0.0016028881073 seconds 

Parecería que es la puesta a cero de la matriz que está teniendo todo el tiempo para numpy. Entonces, a menos que necesites inicializar la matriz, intenta usar empty.

+1

Para ser justo, deberías haber hecho 'pythonTest = timeit.Timer ('a = [0.] * 1000000')', aún funciona más lento que numpy pero es bastante más rápido que un LC. Y está "más cerca" de una lista literal (como se da en la pregunta) porque no ejecuta un ciclo de Python. –

+0

@Rosh Buen punto. Creo que siempre me he apartado del operador '*' para las listas ya que coloca el mismo objeto en cada índice. Aunque dado que los números son inmutables, eso no importa en este caso. Aunque intente realizar una operación masiva en la lista/matriz, Numpy vuelve a estar en la derivación (por ejemplo, arr + = 1). – Dunes

+0

Muy buen punto, gracias. Considerando el resultado, ¿qué sugerirías para arreglos pequeños? Quiero decir, las listas y las tuplas no son realmente agradables cuando se trata de operaciones de matriz básica (como producto vector vectorial, multiplicación de matriz por un número, etc., determinante de matrices pequeñas). Por supuesto, puedo volver a implementar el algos solo, no es el gran problema aquí, pero si ya hay algo para eso, lo considero la solución preferida. –

4

Holy CPU cycles batman!, de hecho.

Pero por favor considere algo muy fundamental relacionado con numpy; funcionalidad basada en álgebra lineal sofisticada (como random numbers o singular value decomposition). Ahora, tenga en cuenta estos cálculos simples seamingly:

In []: A= rand(2560000, 3) 
In []: %timeit rand(2560000, 3) 
1 loops, best of 3: 296 ms per loop 
In []: %timeit u, s, v= svd(A, full_matrices= False) 
1 loops, best of 3: 571 ms per loop 

y por favor créanme que este tipo de rendimiento no será igual manera significativa por cualquier paquete disponible actualmente.

Por lo tanto, describa su problema real, y trataré de encontrar una solución decente numpy para él.

Actualización:
Aquí es algunos simplemente código de intersección esfera de rayos:

import numpy as np 

def mag(X): 
    # magnitude 
    return (X** 2).sum(0)** .5 

def closest(R, c): 
    # closest point on ray to center and its distance 
    P= np.dot(c.T, R)* R 
    return P, mag(P- c) 

def intersect(R, P, h, r): 
    # intersection of rays and sphere 
    return P- (h* (2* r- h))** .5* R 

# set up 
c, r= np.array([10, 10, 10])[:, None], 2. # center, radius 
n= 5e5 
R= np.random.rand(3, n) # some random rays in first octant 
R= R/ mag(R) # normalized to unit length 

# find rays which will intersect sphere 
P, b= closest(R, c) 
wi= b<= r 

# and for those which will, find the intersection 
X= intersect(R[:, wi], P[:, wi], r- b[wi], r) 

Al parecer, se ha calculado correctamente:

In []: allclose(mag(X- c), r) 
Out[]: True 

Y algunos tiempos:

In []: % timeit P, b= closest(R, c) 
10 loops, best of 3: 93.4 ms per loop 
In []: n/ 0.0934 
Out[]: 5353319 #=> more than 5 million detection's of possible intersections/ s 
In []: %timeit X= intersect(R[:, wi], P[:, wi], r- b[wi]) 
10 loops, best of 3: 32.7 ms per loop 
In []: X.shape[1]/ 0.0327 
Out[]: 874037 #=> almost 1 million actual intersections/ s 

Estos tiempos se hacen con una máquina muy modesta. Con la máquina moderna, todavía se puede esperar una aceleración significativa.

De todos modos, esta es solo una breve demostración de cómo codificar con numpy.

+0

mi problema real: http://stackoverflow.com/questions/6528214/improving-performance-of-raytracing-hit-function –

+0

@Stefano Borini: actualicé mi respuesta. Gracias – eat

+0

bueno. Sin embargo, realmente no le permite tratar los objetos de Esfera directamente de esta manera. Debe tener un backend que convierta el diseño de alto nivel en un conjunto agregado de coordenadas que luego se transmitirán a numpy. –

2

Última respuesta, pero podría ser importante para otros espectadores.

Este problema también se ha considerado en el proyecto kwant. De hecho, las matrices pequeñas no están optimizadas en numpy y con bastante frecuencia las matrices pequeñas son exactamente lo que necesita.

En este sentido, crearon un sustituto para matrices pequeñas que se comporta y coexiste con las matrices numpy (cualquier operación no implementada en el nuevo tipo de datos es procesada por numpy).

usted debe buscar en este proyecto:
https://pypi.python.org/pypi/tinyarray/1.0.5
cuyo fin principal es comportan muy bien para los pequeños arreglos. Por supuesto, algunas de las cosas más sofisticadas que puede hacer con numpy no son compatibles con esto. Pero los números parecen ser su pedido.

he hecho algunas pequeñas pruebas:

pitón

he añadido importación numpy para obtener el tiempo de carga correcta

import numpy 

def main(): 
    for i in xrange(2560000): 
     a = [0.0, 0.0, 0.0] 

main() 

numpy

import numpy 

def main(): 
    for i in xrange(2560000): 
     a = numpy.array([0.0, 0.0, 0.0]) 

main() 

numpy cero

import numpy 

def main(): 
    for i in xrange(2560000): 
     a = numpy.zeros((3,1)) 

main() 

tinyarray

import numpy,tinyarray 

def main(): 
    for i in xrange(2560000): 
     a = tinyarray.array([0.0, 0.0, 0.0]) 

main() 

tinyarray cero

import numpy,tinyarray 

def main(): 
    for i in xrange(2560000): 
     a = tinyarray.zeros((3,1)) 

main() 

me corrieron esto:

for f in python numpy numpy_zero tiny tiny_zero ; do 
    echo $f 
    for i in `seq 5` ; do 
     time python ${f}_test.py 
    done 
done 

Y tiene:

python 
python ${f}_test.py 0.31s user 0.02s system 99% cpu 0.339 total 
python ${f}_test.py 0.29s user 0.03s system 98% cpu 0.328 total 
python ${f}_test.py 0.33s user 0.01s system 98% cpu 0.345 total 
python ${f}_test.py 0.31s user 0.01s system 98% cpu 0.325 total 
python ${f}_test.py 0.32s user 0.00s system 98% cpu 0.326 total 
numpy 
python ${f}_test.py 2.79s user 0.01s system 99% cpu 2.812 total 
python ${f}_test.py 2.80s user 0.02s system 99% cpu 2.832 total 
python ${f}_test.py 3.01s user 0.02s system 99% cpu 3.033 total 
python ${f}_test.py 2.99s user 0.01s system 99% cpu 3.012 total 
python ${f}_test.py 3.20s user 0.01s system 99% cpu 3.221 total 
numpy_zero 
python ${f}_test.py 1.04s user 0.02s system 99% cpu 1.075 total 
python ${f}_test.py 1.08s user 0.02s system 99% cpu 1.106 total 
python ${f}_test.py 1.04s user 0.02s system 99% cpu 1.065 total 
python ${f}_test.py 1.03s user 0.02s system 99% cpu 1.059 total 
python ${f}_test.py 1.05s user 0.01s system 99% cpu 1.064 total 
tiny 
python ${f}_test.py 0.93s user 0.02s system 99% cpu 0.955 total 
python ${f}_test.py 0.98s user 0.01s system 99% cpu 0.993 total 
python ${f}_test.py 0.93s user 0.02s system 99% cpu 0.953 total 
python ${f}_test.py 0.92s user 0.02s system 99% cpu 0.944 total 
python ${f}_test.py 0.96s user 0.01s system 99% cpu 0.978 total 
tiny_zero 
python ${f}_test.py 0.71s user 0.03s system 99% cpu 0.739 total 
python ${f}_test.py 0.68s user 0.02s system 99% cpu 0.711 total 
python ${f}_test.py 0.70s user 0.01s system 99% cpu 0.721 total 
python ${f}_test.py 0.70s user 0.02s system 99% cpu 0.721 total 
python ${f}_test.py 0.67s user 0.01s system 99% cpu 0.687 total 

Ahora estas pruebas son (como ya se señaló) no las mejores pruebas. Sin embargo, todavía muestran que tinyarray es más adecuado para matrices pequeñas.
Otro hecho es que las operaciones más comunes deberían ser más rápidas con tinyarray. Por lo tanto, podría tener mejores beneficios de uso que simplemente creaciones de datos.

que nunca han probado en un proyecto de pleno derecho, pero el proyecto kwant lo está utilizando

+0

en una nota lateral, si alguna función 'numpy' está creando demasiada sobrecarga, a veces puede ser beneficioso aplazarla por una sola función, en lugar de buscarla en el módulo, es decir' d = numpy.array; a = d ([0. 0 0.]) '. – zeroth

0

Por supuesto numpy consume más tiempo en este caso, ya que: a = np.array([0.0, 0.0, 0.0]) < = ~ =>a = [0.0, 0.0, 0.0]; a = np.array(a), que dio dos pasos . Pero numpy-array tiene muchas buenas cualidades, su alta velocidad se puede ver en las operaciones sobre ellas, no en la creación de ellas. Parte de mis pensamientos personales :).