Python中的优化点积

Python中的优化点积,python,algorithm,math,Python,Algorithm,Math,两个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模块) 对于下面给出的代码,这些是经过的时间(以秒为单位,一百万次运行): 你能想出一个更快的方法吗 imp

两个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
模块)

对于下面给出的代码,这些是经过的时间(以秒为单位,一百万次运行):

你能想出一个更快的方法吗

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
;我在MacOSX版本10.5.8上使用了Python 2.5.1

编辑:

我运行了N=1000的脚本,结果如下(以秒为单位,运行一百万次):


我想可以肯定的是,选项三确实是最快的,选项二是最慢的(在四个选项中)。

我不知道更快,但我建议:

sum(i*j for i, j in zip(v1, v2))
它更容易阅读,甚至不需要标准的库模块。

为了好玩,我写了一个使用numpy的“d4”:

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

d4过去:56.3278584289#去吧

为了更有趣,我打开了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")
我包括了
检查(v1,v2)
,因为它包含在其他测试中。放弃它会给numpy带来不公平的优势(尽管看起来它可能需要一个)。阵列转换缩短了大约一秒钟(比我想象的要少得多)

我所有的测试都是在N=50的情况下运行的

@nikow:我使用的是numpy 1.0.4,它无疑很旧,很有可能在我安装它后的一年半时间里,它们的性能有所提高


更新#2

@哇,你完全正确。我一定一直在想这些是列表列表之类的东西(我真的不知道我在想什么…+1用于结对编程)

这看起来怎么样:

v3 = array(v1)
v4 = array(v2)
新结果:

d4 elapsed:  3.22535741274
对于Psyco:

d4 elapsed:  2.09182619579
d0仍然在Psyco中获胜,但numpy总体上可能更好,尤其是在数据集更大的情况下


昨天我有点担心我缓慢的numpy结果,因为据推测numpy用于大量计算,并进行了大量优化。显然,我还没来得及检查我的结果:)

这里是与numpy的比较。我们将快速星图方法与
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 Ndaray:

$ 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函数构造。

请对这个“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
(在Python2.x上,我假设您使用的)在计算之前不必要地创建了一个虚拟列表。

@Arrieta:您可以通过将“from dot_product”替换为“from main”来删除文件名为dot_product.py的要求。@unutbu:当然,我只是觉得用这个名称保存文件以便快速运行比修改脚本更简单。谢谢。我的结果是:d0经过时间:13.4328830242 d1经过时间:9.52215504646 d2经过时间:10.1050257683 d3经过时间:9.16764998436请务必检查d1和d3之间的差异是否具有统计学意义。@liori:没错。我正在运行N=1000的问题,并期望看到更大的差异。如果您重复执行点积,保持其中一个向量不变,那么动态编译方法可能值得研究。固定部分为0的所有术语都可以完全删除,如果固定部分为1,则乘法可以消除。@SilentGhost:您的方法需要更长的时间。对于N=10,需要18.0258秒(一百万次运行)。我要的是速度;实际上,可读性不是问题,因为点积是从函数(udotv=dot(u,v))调用的,我可以在dot的定义中对代码进行尽可能多的注释。您的方法确实不合适。@SilentGhost,一个快速观察:将zip更改为itertools.izip将时间减少到15.84879。也许值得知道?这绝对是我要做的。如果有问题的话,我会加入psyco来提高Windows的性能。没有psyco:18.6840143091。与Psyco:25.0433867992。出于某种原因,这一定是psyco的“最坏情况”优化之一。使用izip()(不带psyco)只将其降到了17.4570938485。伟大的发现,赛斯!首先,令人难以置信的是Numpy的速度如此之慢!我希望会快得多。另外,我对Psyco一无所知(我认为自己是Python迷!)-谢谢你指出这一点,我会彻底检查一下。最后,有趣的是,基本上,Psyco为d0编写了纯优化的C代码,但不知道如何优化d3。我想这意味着,如果你想使用Psyco,你应该设计好算法,以便对其进行优化(而不是在Python结构后面“隐藏”其逻辑)。再一次,伟大的发现!也许你的numpy安装有问题。在我的机器上,numpy比其他选项快得多(我没有尝试psyco)。N=50对于numpy来说有点小,可以显示它的力量。你做错了。创建一次numpy数组(而不是每次传递将由numpy转换的列表),numpy将更快。同时也不要付账。你做得非常错误。您正在将列表传递给numpy。事实上,这是一个单元素numpy数组的列表。感谢您的更新!只是
$ 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
$ 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
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))