更快的评估函数调用

时间:2017-11-18 13:55:30

标签: python performance function numpy optimization

我有一个需要至少评估1e6次的功能。它只需要两个数组作为输入。

import numpy as np
np.random.seed(1)

res = np.random.rand(0,1,1000)
f1 = lambda r, sig: -0.5*(np.sum(2*np.log(sig) + np.log(2*np.pi) + (r/sig)**2))    

目前,

%timeit f1(res,np.sqrt(res)) 

在26 usec时钟。有没有办法让我可以更快地进行评估调用/执行时间?

任何手段都可以;例如不使用lambda函数,使用本机数学而不是numpy,或任何涉及使用python中实现的更快数学运算操作方程式的技巧。

编辑:

我犯了一个生成空res数组的愚蠢错误。所以它应该是

res=np.random.rand(1000)

此外,第二个数组np.sqrt(res)实际上独立于res,并且通常由远小于res的值组成,但我使用sqrt(res)仅用于说明。

所以更清楚地重写代码

import numpy as np
np.random.seed(1)

res1 = np.random.rand(1000)
res2 = np.sqrt(res) #not true in general but res2<res1
f1 = lambda r, sig: -0.5*(np.sum(2*np.log(sig) + np.log(2*np.pi) + (r/sig)**2)) 

现在%f1(res1,res2)在我的机器中以60 usec的速度计时。

4 个答案:

答案 0 :(得分:2)

作为参考,[setVisibleMapRect: edgePadding:]你定义的方式给了我:

  

10000次循环,最佳3:每循环20.9μs

一种简单的优化,可以避免每次计算f1

np.log(2*np.pi)

log2pi = np.log(2*np.pi) f2 = lambda r, sig: -0.5*(np.sum(2*np.log(sig) + log2pi + (r/sig)**2)) 输出减少约15%:

  

100000个循环,最佳3:每循环17.8μs

此外,计算timeit通常比x*x更快。使用这种方法,您也可以避免x**2操作。

2*
  

100000个循环,最佳3:每循环15μs

此外,根据您使用参数def f3(r, sig): sig2 = sig * sig return -0.5*(np.sum(np.log(sig2) + log2pi + (r*r/sig2))) 调用此函数的频率,您绝对可以考虑将其简化为:

res, np.sqrt(res)

您可以检查f4 = lambda a: -0.5*np.sum(np.log(a) + log2pi + a) 并且它的速度也明显更快(约44%)。

  

100000个循环,最佳3:每循环11.7μs

答案 1 :(得分:1)

实际上,您可以跳过np.sum(),因为您通过应用+运算符获得的单个参数调用它。此外,np.log(2*np.pi)是常量并且不依赖于lambda args,因此您可以在lambda之外计算并重用。此外,这可能不会有太大变化,但请检查a**2是否比a*a更快或更慢;如果后者更快,请计算d = r/sig一次并使用d*d代替(r/sig)**2。您还可以检查0.5*x是否比x/2.0更快或更慢。这很多,但它可能会让你加速一两次。

现在,有些数学。 根据上述建议,您得到:

log_of_pi = np.log(2*np.pi)
f1 = lambda r, sig: -0.5*(2*np.log(sig) + log_of_pi + (r/sig)**2))
f1(res,np.sqrt(res)) 

所以基本上,你计算:

c1*(2*log(sqrt(r)) + c2 + (r/sqrt(r))**2)

首先:

log(sqrt(r)) = log(r**(1/2)) = 1/2*log(r)

此外:

(r/sqrt(r))**2 = sqrt(r)**2 = abs(r)

基本上,你正试图计算:

c1*(2*log(sqrt(r)) + c2 + (r/sqrt(r))**2) = c1*(2*1/2*log(r) + c2 + abs(r)) = c1*(log(r) + c2 + abs(r))

所以,你可以跳过昂贵的除法平方(和的最后一部分)并计算r的平方根(在调用f1之前完成)。

当然,这是假设您始终将sqrt(r)用作sig

PS。尊重grovina,在我之前15秒,answered有同样的思路,并提供了测量结果。你想接受我的答案,接受另一个,因为它更完整。

答案 2 :(得分:1)

我很确定最贵的东西都是log,以及那些你可以保存的东西(即调用一次而不是sig.size - 在这个例子中= 1000次)。基础身份是log(a) + log(b) = log(a*b)。给我加速三倍。

顺便说一下。我将生成空数组的random.rand(0, 1, 1000)更改为random.rand(1000)。我也认为sig=sqrt(res)不是一般性的假设,所以我没有试图利用它。

我还做了一些其他的优化,但是它们的效果远不如摆脱log那么大。 1)预计算log(2*pi) 2)在最后一个学期使用np.dot

更新

正如@JPdL所指出的,这种方法很容易受到过度和下溢的影响。可以使用分块交换各种性能以防止这种情况,请参阅下面的f4. f5. f6

import numpy as np
np.random.seed(1)

res = np.random.rand(1000)
f1 = lambda r, sig: -0.5*(np.sum(2*np.log(sig) + np.log(2*np.pi) + (r/sig)**2)) 

def f2(r, sig):
    return -0.5*(np.sum(2*np.log(sig) + np.log(2*np.pi) + (r/sig)**2))

# gung ho
def f3(r, sig, precomp=np.log(2*np.pi)):
    rs = r/sig
    return -np.log(np.prod(sig)) - 0.5*(r.size * precomp + np.dot(rs, rs))

# chunk and hope for the best
def f4(r, sig, chnk=32, precomp=np.log(2*np.pi)):
    rs = r/sig
    sumlogsig = np.log(np.multiply.reduceat(sig, np.arange(0, len(r), chnk))).sum()
    return -sumlogsig - 0.5*(r.size * precomp + np.dot(rs, rs))

# chunk and check for extreme values
def f5(r, sig, chnk=32,
       precomp=np.log(2*np.pi), precomp2=np.exp([-8, 8])):
    rs = r/sig
    bad = np.where((sig<precomp2[0]) | (sig>precomp2[1]))[0]
    sumlogsig = np.log(sig[bad]).sum()
    keep = sig[bad]
    sig[bad] = 1
    sumlogsig += np.log(np.multiply.reduceat(sig, np.arange(0, len(r), chnk))).sum()
    sig[bad] = keep
    return -sumlogsig - 0.5*(r.size * precomp + np.dot(rs, rs))

# chunk and try to be extra clever
def f6(r, sig, chnk=512,
       precomp=np.log(2*np.pi), precomp2=np.exp(np.arange(-18, 19))):
    binidx = np.digitize(sig, precomp2[1::2])<<1
    rs = r/sig
    sig = sig*precomp2[36 - binidx]
    bad = np.where((binidx==0) | (binidx==36))[0]
    sumlogsig = binidx.sum() - 18*r.size + np.log(sig[bad]).sum()
    sig[bad] = 1
    sumlogsig += np.log(np.multiply.reduceat(sig, np.arange(0, len(r), chnk))).sum()
    return -sumlogsig - 0.5*(r.size * precomp + np.dot(rs, rs))

from timeit import timeit
sr = np.sqrt(res)
print(timeit('f1(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f1':f1}))
print(timeit('f2(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f2':f2}))
print(timeit('f3(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f3':f3}))
print(timeit('f4(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f4':f4}))
print(timeit('f5(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f5':f5}))
print(timeit('f6(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f6':f6}))
print(f1(res,np.sqrt(res)))
print(f2(res,np.sqrt(res)))
print(f3(res,np.sqrt(res)))
print(f4(res,np.sqrt(res)))
print(f5(res,np.sqrt(res)))
print(f6(res,np.sqrt(res)))

示例输出:

0.004246247990522534
0.00418912700843066
0.0011273059935774654
0.0013386670034378767
0.0022679700050503016
0.004274581006029621
-662.250886322
-662.250886322
-662.250886322
-662.250886322
-662.250886322
-662.250886322

答案 3 :(得分:1)

您正在使用lambda进行隐式复制。您可以尽可能使用函数的就地变体来提升性能。 http://ipython-books.github.io/featured-01/

def f2(r, sig):
   x = np.log(sig)
   x *= 2  # in-place operation

   x += np.log(2*np.pi)  # in-place operation

   y = r / sig
   y **= 2  # in-place operation

   x += y  # in-place operation

   return -0.5 * x.sum()

另外,检查

的输出
import numpy.distutils.system_info as sysinfo
sysinfo.show_all()

...

openblas_lapack_info:
  libraries openblas not found in ['C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python35-32\\lib', 'C:\\', 'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python35-32\\libs']
  NOT AVAILABLE

如果blas,lapack和atlas是NOT AVAILABLE那么你就没有从numpy中获得最大收益。不是由一个长镜头。这些库使numpy能够使用SIMD操作(单指令多数据 - 一种并行化形式)。