2009-12-01 22 views
11

El producto escalar de dos vectores n-dimensionales u=[u1,u2,...un] y v=[v1,v2,...,vn] es proporcionado por u1*v1 + u2*v2 + ... + un*vn.Producto de punto optimizado en Python

Una pregunta posted yesterday me animó a encontrar la forma más rápida de calcular productos de puntos en Python utilizando solo la biblioteca estándar, sin módulos de terceros o llamadas C/Fortran/C++.

Discutí cuatro enfoques diferentes; hasta ahora, el más rápido parece ser sum(starmap(mul,izip(v1,v2))) (donde starmap y izip provienen del módulo itertools).

Para el código presentado a continuación, estos son los tiempos transcurrido (en segundos, para un millón de carreras):

d0: 12.01215 
d1: 11.76151 
d2: 12.54092 
d3: 09.58523 

¿Se puede pensar en una manera más rápida de hacer esto?

import timeit # module with timing subroutines                
import random # module to generate random numnbers               
from itertools import imap,starmap,izip 
from operator import mul 

def v(N=50,min=-10,max=10): 
    """Generates a random vector (in an array) of dimension N; the           
    values are integers in the range [min,max].""" 
    out = [] 
    for k in range(N): 
     out.append(random.randint(min,max)) 
    return out 

def check(v1,v2): 
    if len(v1)!=len(v2): 
     raise ValueError,"the lenght of both arrays must be the same" 
    pass 

def d0(v1,v2): 
    """                          
    d0 is Nominal approach:                     
    multiply/add in a loop                     
    """ 
    check(v1,v2) 
    out = 0 
    for k in range(len(v1)): 
     out += v1[k] * v2[k] 
    return out 

def d1(v1,v2): 
    """                          
    d1 uses an imap (from itertools)                   
    """ 
    check(v1,v2) 
    return sum(imap(mul,v1,v2)) 

def d2(v1,v2): 
    """                          
    d2 uses a conventional map                    
    """ 
    check(v1,v2) 
    return sum(map(mul,v1,v2)) 

def d3(v1,v2): 
    """                          
    d3 uses a starmap (itertools) to apply the mul operator on an izipped (v1,v2)       
    """ 
    check(v1,v2) 
    return sum(starmap(mul,izip(v1,v2))) 

# generate the test vectors                     
v1 = v() 
v2 = v() 

if __name__ == '__main__': 

    # Generate two test vectors of dimension N                

    t0 = timeit.Timer("d0(v1,v2)","from dot_product import d0,v1,v2") 
    t1 = timeit.Timer("d1(v1,v2)","from dot_product import d1,v1,v2") 
    t2 = timeit.Timer("d2(v1,v2)","from dot_product import d2,v1,v2") 
    t3 = timeit.Timer("d3(v1,v2)","from dot_product import d3,v1,v2") 

    print "d0 elapsed: ", t0.timeit() 
    print "d1 elapsed: ", t1.timeit() 
    print "d2 elapsed: ", t2.timeit() 
    print "d3 elapsed: ", t3.timeit() 

en cuenta que el nombre del archivo debe ser dot_product.py para la secuencia de comandos para ejecutar; Utilicé Python 2.5.1 en Mac OS X Versión 10.5.8.

EDIT:

me encontré con el guión de N = 1000 y estos son los resultados (en segundos, de un millón de carreras):

d0: 205.35457 
d1: 208.13006 
d2: 230.07463 
d3: 155.29670 

supongo que es seguro asumir que, de hecho, , la opción tres es la más rápida y la opción dos la más lenta (de las cuatro presentadas).

+0

@Arrieta: Puede eliminar el requisito de que el archivo se llame dot_product.py reemplazando 'de dot_product' con 'from __main__'. – unutbu

+0

@unutbu: Claro, creo que es más simple guardar el archivo con ese nombre para una ejecución rápida que alterar el script. Gracias. – Escualo

+1

Mis resultados son: d0 transcurrido: 13.4328830242 d1 transcurrido: 9.52215504646 d2 transcurrido: 10.1050257683 d3 transcurrido: 9.16764998436 Asegúrese de verificar si las diferencias entre d1 y d3 son estadísticamente significativas. – liori

Respuesta

8

Sólo por diversión que escribió un "D4", que utiliza numpy:

from numpy import dot 
def d4(v1, v2): 
    check(v1, v2) 
    return dot(v1, v2) 

Mis resultados (Python 2.5.1, XP Pro SP3, 2 GHz Core 2 Duo T7200):

d0 elapsed: 12.1977242918 
d1 elapsed: 13.885232341 
d2 elapsed: 13.7929552499 
d3 elapsed: 11.0952246724 

d4 transcurrido: 56.3278584289 # ir numpy!

Y, para divertirse aún más, encendí psyco:

d0 elapsed: 0.965477735299 
d1 elapsed: 12.5354792299 
d2 elapsed: 12.9748163524 
d3 elapsed: 9.78255448667 

d4 transcurrido: 54.4599059378

Basado en esto, DECLARO D0 el ganador :)


Actualización

@kaiser.SE: probablemente debería haber mencionado que me convierto todo para numpy matrices primera:

from numpy import array 
v3 = [array(vec) for vec in v1] 
v4 = [array(vec) for vec in v2] 

# then 
t4 = timeit.Timer("d4(v3,v4)","from dot_product import d4,v3,v4") 

Y yo incluido check(v1, v2) ya que está incluido en las otras pruebas. Si lo dejamos, le daría a numpy una ventaja injusta (aunque parece que podría usar uno). La conversión de matriz se redujo aproximadamente un segundo (mucho menos de lo que pensé que sería).

Todas mis pruebas se realizaron con N = 50.

@nikow: Estoy usando numpy 1.0.4, que sin duda es viejo, sin duda es posible que hayan mejorado el rendimiento en el último año y medio desde que lo instalé.


Actualización # 2

@ kaiser.se Vaya, usted es totalmente correcto. Debo haber estado pensando que estas eran listas de listas o algo así (realmente no tengo idea de lo que estaba pensando ... +1 para la programación de pares).

¿cómo es esta:

v3 = array(v1) 
v4 = array(v2) 

Nuevos resultados:

d4 elapsed: 3.22535741274 

Con Psyco:

d4 elapsed: 2.09182619579 

d0 todavía gana con Psyco, pero numpy es probablemente mejor en general, especialmente con conjuntos de datos más grandes.

Ayer me molestó un poco mi resultado numpy lento, ya que presumiblemente numpy se usa para mucho de cálculo y ha tenido mucha optimización. Obviamente, sin embargo, no se molestó en comprobar mi resultado :)

+0

¡Grandes hallazgos, Seth! Primero, ¡es increíble que Numpy sea tan lento! Yo esperaría que sea mucho más rápido. Además, no tenía ni idea de Psyco (¡y me consideraba un adicto a Python!). Gracias por señalarlo, lo comprobaré definitivamente. Finalmente, es interesante ver que, básicamente, la cosa de Psyco hizo el código C puro optimizado para d0 y no sabía cómo optimizar d3. Supongo que el mensaje es que, si quieres usar Psyco, debes diseñar el algoritmo para que se pueda optimizar (en lugar de "ocultar" su lógica detrás de las construcciones de Python). De nuevo, grandes hallazgos! – Escualo

+0

Quizás algo esté mal con tu instalación numpy. En mi máquina numpy es mucho más rápido que las otras opciones (no probé psyco). Y N = 50 es un poco pequeño para que numpy muestre su fuerza. – nikow

+5

lo estás haciendo mal. hacer numpy arrays una vez (en lugar de pasar listas que se convertirán por numpy * cada vez *), y numpy será mucho más rápido. también deje caer el cheque. – u0b34a0f6ae

4

No sé acerca más rápido, pero me gustaría sugerir:

sum(i*j for i, j in zip(v1, v2)) 

es mucho más fácil de leer y no requiere siquiera módulos estándar de la biblioteca.

+0

@SilentGhost: su enfoque lleva mucho más tiempo. Para N = 10, tomó 18.0258 segundos (un millón de ejecuciones). Lo que estoy buscando es velocidad; de hecho, la legibilidad no es un problema, ya que el producto escalar se llama desde una función (udotv = punto (u, v)), y puedo comentar el código tanto como sea necesario en la definición de punto. Su enfoque realmente no es apropiado. – Escualo

+1

@ SilentGhost, una rápida reserva: al cambiar el zip a itertools.izip se reduce el tiempo a 15.84879. Quizás valga la pena saberlo? – Escualo

+3

si el rendimiento es tan importante, escríbalo en C – SilentGhost

3

Aquí está una comparación con numpy. Comparamos el enfoque starmap rápida con numpy.dot

En primer lugar, iteración sobre las listas de Python normales:

$ python -mtimeit "import numpy as np; r = range(100)" "np.dot(r,r)" 
10 loops, best of 3: 316 usec per loop 

$ python -mtimeit "import operator; r = range(100); from itertools import izip, starmap" "sum(starmap(operator.mul, izip(r,r)))" 
10000 loops, best of 3: 81.5 usec per loop 

Entonces numpy ndarray:

$ python -mtimeit "import numpy as np; r = np.arange(100)" "np.dot(r,r)" 
10 loops, best of 3: 20.2 usec per loop 

$ python -mtimeit "import operator; import numpy as np; r = np.arange(100); from itertools import izip, starmap;" "sum(starmap(operator.mul, izip(r,r)))" 
10 loops, best of 3: 405 usec per loop 

Al ver esto, parece numpy en matrices numpy es más rápido, seguido de construcciones funcionales de Python trabajando con listas.

0

Compare esta función "d2a" y compárela con su función "d3".

from itertools import imap, starmap 
from operator import mul 

def d2a(v1,v2): 
    """ 
    d2a uses itertools.imap 
    """ 
    check(v1,v2) 
    return sum(imap(mul, v1, v2)) 

map (en Python 2.x, que es lo que supongo que usa) innecesariamente crea una lista ficticia antes del cálculo.

Cuestiones relacionadas