【问题标题】:Optimized dot product in PythonPython中优化的点积
【发布时间】:2009-12-01 19:13:31
【问题描述】:

两个n维向量u=[u1,u2,...un]v=[v1,v2,...,vn]的点积由u1*v1 + u2*v2 + ... + un*vn给出。

posted yesterday 的一个问题鼓励我找到在 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

我想可以肯定地假设,实际上,选项三是最快的,而选项二是最慢的(在所提出的四个中)。

【问题讨论】:

  • @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 的乘法可以去掉。

标签: python algorithm math


【解决方案1】:

只是为了好玩,我写了一个使用 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):

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

d4 elapsed: 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),因为它包含在其他测试中。不使用它会给 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 用于很多计算并且进行了很多优化。显然,没有足够的精力来检查我的结果:)

【讨论】:

  • 伟大的发现,赛斯!首先,令人难以置信的是,Numpy 这么慢!我希望它会更快。另外,我对 Psyco 毫无头绪(我认为自己是 Python 迷!) - 感谢您指出,我会明确地检查一下。最后,有趣的是,基本上,Psyco 的东西为 d0 做了纯优化的 C 代码,不知道如何优化 d3。我猜想传达的信息是,如果您想使用 Psyco,您应该布置算法以便对其进行优化(而不是“隐藏”其逻辑在 Python 构造后面)。再次,伟大的发现!
  • 您的 numpy 安装可能有问题。在我的机器上,numpy 比其他选项快得多(我没有尝试 psyco)。而N=50对于numpy来说有点小才能显示出它的实力。
  • 你做错了。制作一次 numpy 数组(而不是传递将由 numpy 每次转换的列表),并且 numpy 会快得多。也放弃支票。
  • 你这样做非常错了。您正在将一个列表传递给 numpy。实际上是单元素 numpy 数组的列表。
  • 感谢您的更新!又是一个很难正确使用 numpy 的例子。
【解决方案2】:

这里是与 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 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 函数构造。

【讨论】:

    【解决方案3】:

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

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

    它更容易阅读,甚至不需要标准库模块。

    【讨论】:

    • @SilentGhost:你的方法需要更长的时间。对于 N=10,它花费了 18.0258 秒(一百万次运行)。我正在寻找的是速度;确实可读性不是问题,因为点积是从函数调用的(udotv=dot(u,v)),我可以在点的定义中尽可能多地注释代码。你的做法确实不合适。
    • @SilentGhost,一个快速的观察:将 zip 更改为 itertools.izip 将时间减少到 15.84879。也许值得知道?
    • 这绝对是我会做的。如果这是一个问题,我会在 Windows 上投入 psyco 以获得性能。
    • 无心理医生:18.6840143091。使用 Psyco:25.0433867992。由于某种原因,这一定是 psyco 的“最坏情况”优化之一。使用 izip()(不带 psyco)只能将其降低到 17.4570938485。
    【解决方案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 上,我假设您使用它)在计算之前不必要地创建了一个虚拟列表。

    【讨论】:

      【解决方案5】:

      在 Mathematica 中(10^12 次加法和乘法):

      a = RandomReal[1,{10^4,10^4}];
      b = RandomReal[1,{10^4,10^4}];
      AbsoluteTiming[c=a.b;]//First
      

      9.65295 秒

      (Windows 10、戴尔 XPS17 9700、Mathematica 12.3)

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-14
      • 2019-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多