Оптимизированный точечный продукт в Python - PullRequest
12 голосов
/ 01 декабря 2009

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

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

Я рассчитал четыре разных подхода; пока что самым быстрым кажется 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; Я использовал Python 2.5.1 на Mac OS X версии 10.5.8.

EDIT:

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

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

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

Ответы [ 4 ]

9 голосов
/ 01 декабря 2009

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

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

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

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

Прошло d4: 56.3278584289 # Ступай!

И, для еще большего удовольствия, я включил психо:

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

d4 истекло: 54,4599059378

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


Обновление

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

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), поскольку он включен в другие тесты. Отказ от этого дал бы NumPy несправедливое преимущество (хотя, похоже, он мог бы его использовать). Преобразование массива сбрасывается примерно на секунду (гораздо меньше, чем я думал).

Все мои тесты были выполнены с 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 используется для большого количества вычислений и имеет много оптимизации Хотя, очевидно, недостаточно обеспокоен, чтобы проверить мой результат:)

5 голосов
/ 02 декабря 2009

Вот сравнение с 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 голосов
/ 01 декабря 2009

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

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

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

0 голосов
/ 23 декабря 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...