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