Python中的优化点乘

12 投票
5 回答
18845 浏览
提问于 2025-04-15 16:34

两个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)))(其中 starmapizip 是来自 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,这样脚本才能运行;我在Mac OS X 10.5.8上使用的是Python 2.5.1。

编辑:

我对N=1000运行了这个脚本,结果是(单位:秒,运行一百万次):

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

我想可以安全地假设,确实,第三种方法是最快的,而第二种方法是四种中最慢的。

5 个回答

4

我不知道有没有更快的方法,但我建议:

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

这样写起来更容易理解,而且连标准库的模块都不需要用。

5

这里是一个关于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的函数式方法处理列表。

9

为了好玩,我写了一个叫“d4”的程序,它使用了numpy库:

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

我的测试结果(Python 2.5.1,XP专业版sp3,2GHz Core2 Duo T7200):

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

d4运行时间: 56.3278584289 # 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),因为它也在其他测试中。如果不加这个,numpy就会有不公平的优势(虽然看起来它确实需要一个)。数组转换大约节省了1秒(比我想象的少得多)。

我的所有测试都是用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显然用于很多计算,并且经过了很多优化。不过显然,我并没有不满到去检查我的结果 :)

撰写回答