Оптимизированный точечный продукт в Python

Точечное произведение двух n-мерных векторов u=[u1,u2,...un] и v=[v1,v2,...,vn] дается выражением u1*v1 + u2*v2 + ... + un*vn.

Вопрос опубликованный вчера, побудил меня найти самый быстрый способ вычислить точечные продукты на Python, используя только стандартную библиотеку, сторонние модули или C/Fortran/С++.

Я приурочил четыре разных подхода; до сих пор наиболее быстрым кажется sum(starmap(mul,izip(v1,v2))) (где starmap и izip поступают из модуля itertools).

Для приведенного ниже кода это истекшее время (в секундах, для одного миллиона прогонов):

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

Можете ли вы придумать более быстрый способ сделать это?

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()

Обратите внимание, что имя файла должно быть dot_product.py для запуска script; Я использовал Python 2.5.1 в Mac OS X версии 10.5.8.

EDIT:

Я запустил script для N = 1000, и это результаты (в секундах, для миллиона прогонов):

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

Я думаю, можно с уверенностью предположить, что, действительно, вариант три является самым быстрым, а второй вариант - самым медленным (из четырех представленных).

Ответ 1

Просто для удовольствия я написал "d4", который использует numpy:

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

Мои результаты (Python 2.5.1, XP Pro sp3, 2GHz Core2 Duo T7200):

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

d4 прошло: 56.3278584289 # go numpy!

И, еще веселее, я включил psyco:

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

d4 прошло: 54.4599059378

Исходя из этого, я объявляю d0 победителем:)


Обновление

@kaiser.se: Возможно, я должен был упомянуть, что сначала преобразовал все в массивы numpy:

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")

И я включил check(v1, v2), так как он включался в другие тесты. Оставив это, вы получите неохотное преимущество (хотя похоже, что он может использовать его). Преобразование массива сократилось примерно на секунду (намного меньше, чем я думал).

Все мои тесты выполнялись с N = 50.

@nikow: Я использую numpy 1.0.4, который, несомненно, является старым, возможно, что они улучшили производительность за последние полтора года с тех пор, как я его установил.


Обновление # 2

@kaiser.se Вау, ты совершенно прав. Должно быть, я думал, что это списки списков или что-то (я действительно не знаю, что я думал... +1 для парного программирования).

Как это выглядит:

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

Новые результаты:

d4 elapsed:  3.22535741274

С Psyco:

d4 elapsed:  2.09182619579

d0 все еще выигрывает с Psyco, но numpy, вероятно, лучше в целом, особенно с большими наборами данных.

Вчера меня немного беспокоил мой медленный результат numpy, так как предположительно numpy используется для большого количества вычислений и имеет большую оптимизацию. Очевидно, хотя, не надоело, чтобы проверить мой результат:)

Ответ 2

Я не знаю быстрее, но я бы предложил:

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

его гораздо легче читать и не требует даже стандартных библиотечных модулей.

Ответ 3

Вот сравнение с numpy. Мы сравниваем быстрый метод starmap с numpy.dot

Во-первых, итерация по нормальным спискам Python:

$ 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

Затем 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

Увидев это, кажется, что numpy на массивах numpy является самым быстрым, за ним следуют функциональные конструкции python, работающие со списками.

Ответ 4

Пожалуйста, сравните эту функцию "d2a" и сравните ее с вашей функцией "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 (на Python 2.x, который, как я полагаю, вы используете), излишне создает список фиктивных данных перед вычислением.